summaryrefslogtreecommitdiffstats
path: root/layout/base
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/base
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/base')
-rw-r--r--layout/base/AccessibleCaret.cpp392
-rw-r--r--layout/base/AccessibleCaret.h257
-rw-r--r--layout/base/AccessibleCaretEventHub.cpp829
-rw-r--r--layout/base/AccessibleCaretEventHub.h226
-rw-r--r--layout/base/AccessibleCaretLogger.h27
-rw-r--r--layout/base/AccessibleCaretManager.cpp1474
-rw-r--r--layout/base/AccessibleCaretManager.h347
-rw-r--r--layout/base/ActiveLayerTracker.cpp555
-rw-r--r--layout/base/ActiveLayerTracker.h141
-rw-r--r--layout/base/ArenaObjectID.h36
-rw-r--r--layout/base/ArenaRefPtr.h167
-rw-r--r--layout/base/ArenaRefPtrInlines.h47
-rw-r--r--layout/base/BorderCache.h75
-rw-r--r--layout/base/BorderConsts.h28
-rw-r--r--layout/base/CaretAssociationHint.h22
-rw-r--r--layout/base/DashedCornerFinder.cpp427
-rw-r--r--layout/base/DashedCornerFinder.h277
-rw-r--r--layout/base/DisplayItemClip.cpp476
-rw-r--r--layout/base/DisplayItemClip.h191
-rw-r--r--layout/base/DisplayItemScrollClip.cpp57
-rw-r--r--layout/base/DisplayItemScrollClip.h126
-rw-r--r--layout/base/DisplayListClipState.cpp227
-rw-r--r--layout/base/DisplayListClipState.h468
-rw-r--r--layout/base/DottedCornerFinder.cpp568
-rw-r--r--layout/base/DottedCornerFinder.h438
-rw-r--r--layout/base/FrameLayerBuilder.cpp6378
-rw-r--r--layout/base/FrameLayerBuilder.h737
-rw-r--r--layout/base/FramePropertyTable.cpp239
-rw-r--r--layout/base/FramePropertyTable.h442
-rw-r--r--layout/base/GeometryUtils.cpp399
-rw-r--r--layout/base/GeometryUtils.h65
-rw-r--r--layout/base/LayerState.h26
-rw-r--r--layout/base/LayoutLogging.cpp33
-rw-r--r--layout/base/LayoutLogging.h64
-rw-r--r--layout/base/MaskLayerImageCache.cpp70
-rw-r--r--layout/base/MaskLayerImageCache.h289
-rw-r--r--layout/base/MobileViewportManager.cpp415
-rw-r--r--layout/base/MobileViewportManager.h97
-rw-r--r--layout/base/OverflowChangedTracker.h215
-rw-r--r--layout/base/PaintTracker.cpp11
-rw-r--r--layout/base/PaintTracker.h34
-rw-r--r--layout/base/PositionedEventTargeting.cpp656
-rw-r--r--layout/base/PositionedEventTargeting.h32
-rw-r--r--layout/base/RestyleLogging.h48
-rw-r--r--layout/base/RestyleManager.cpp3974
-rw-r--r--layout/base/RestyleManager.h885
-rw-r--r--layout/base/RestyleManagerBase.cpp1378
-rw-r--r--layout/base/RestyleManagerBase.h169
-rw-r--r--layout/base/RestyleManagerHandle.h218
-rw-r--r--layout/base/RestyleManagerHandleInlines.h195
-rw-r--r--layout/base/RestyleTracker.cpp493
-rw-r--r--layout/base/RestyleTracker.h373
-rw-r--r--layout/base/RestyleTrackerInlines.h23
-rw-r--r--layout/base/ScrollbarStyles.cpp32
-rw-r--r--layout/base/ScrollbarStyles.h82
-rw-r--r--layout/base/ServoRestyleManager.cpp594
-rw-r--r--layout/base/ServoRestyleManager.h133
-rw-r--r--layout/base/StackArena.cpp179
-rw-r--r--layout/base/StackArena.h94
-rw-r--r--layout/base/StaticPresData.cpp311
-rw-r--r--layout/base/StaticPresData.h160
-rw-r--r--layout/base/TouchManager.cpp291
-rw-r--r--layout/base/TouchManager.h63
-rw-r--r--layout/base/UnitTransforms.h294
-rw-r--r--layout/base/Units.h654
-rw-r--r--layout/base/WordMovementType.h14
-rw-r--r--layout/base/ZoomConstraintsClient.cpp253
-rw-r--r--layout/base/ZoomConstraintsClient.h47
-rw-r--r--layout/base/crashtests/1001237.html10
-rw-r--r--layout/base/crashtests/1009036.html15
-rw-r--r--layout/base/crashtests/1043163-1.html2
-rw-r--r--layout/base/crashtests/1061028.html9
-rw-r--r--layout/base/crashtests/1107508-1.html18
-rw-r--r--layout/base/crashtests/1116104.html15
-rw-r--r--layout/base/crashtests/1127198-1.html5
-rw-r--r--layout/base/crashtests/1140198.html16
-rw-r--r--layout/base/crashtests/1143535.html6
-rw-r--r--layout/base/crashtests/1156588.html29
-rw-r--r--layout/base/crashtests/1162813.xul17
-rw-r--r--layout/base/crashtests/1163583.html14
-rw-r--r--layout/base/crashtests/118931-1.html7
-rw-r--r--layout/base/crashtests/121533-1.html11
-rw-r--r--layout/base/crashtests/123049-1.html12
-rw-r--r--layout/base/crashtests/1234622-1.html17
-rw-r--r--layout/base/crashtests/1235467-1.html8
-rw-r--r--layout/base/crashtests/123946-1.html10
-rw-r--r--layout/base/crashtests/1261351-iframe.html26
-rw-r--r--layout/base/crashtests/1261351.html7
-rw-r--r--layout/base/crashtests/1270797-1.html9
-rw-r--r--layout/base/crashtests/1270797-1.jpgbin0 -> 3595 bytes
-rw-r--r--layout/base/crashtests/1278455-1.html11
-rw-r--r--layout/base/crashtests/1286889.html2
-rw-r--r--layout/base/crashtests/128855-1.html8
-rw-r--r--layout/base/crashtests/1288608.html18
-rw-r--r--layout/base/crashtests/1297835.html6
-rw-r--r--layout/base/crashtests/1299736-1.html15
-rw-r--r--layout/base/crashtests/1308793.svg31
-rw-r--r--layout/base/crashtests/1308848-1.html10
-rw-r--r--layout/base/crashtests/1308848-2.html10
-rw-r--r--layout/base/crashtests/133410-1.html27
-rw-r--r--layout/base/crashtests/1343606.html39
-rw-r--r--layout/base/crashtests/143862-1a-inner.html19
-rw-r--r--layout/base/crashtests/143862-1a.html7
-rw-r--r--layout/base/crashtests/143862-1b-inner.html17
-rw-r--r--layout/base/crashtests/143862-1b.html7
-rw-r--r--layout/base/crashtests/143862-1c-inner.html17
-rw-r--r--layout/base/crashtests/143862-1c.html7
-rw-r--r--layout/base/crashtests/143862-2.html15
-rw-r--r--layout/base/crashtests/147320-1.html7
-rw-r--r--layout/base/crashtests/148245-1.html11
-rw-r--r--layout/base/crashtests/149014-1.html44
-rw-r--r--layout/base/crashtests/150431-1.html7
-rw-r--r--layout/base/crashtests/176915-1.html10
-rw-r--r--layout/base/crashtests/191272-1.html13
-rw-r--r--layout/base/crashtests/199696-1.html33
-rw-r--r--layout/base/crashtests/217903-1.html5
-rw-r--r--layout/base/crashtests/223064-1.html11
-rw-r--r--layout/base/crashtests/234851-1.html14
-rw-r--r--layout/base/crashtests/234851-2.html35
-rw-r--r--layout/base/crashtests/241300-1.html5
-rw-r--r--layout/base/crashtests/243159-1.html4
-rw-r--r--layout/base/crashtests/243159-2.xhtml26
-rw-r--r--layout/base/crashtests/243519-1.html30
-rw-r--r--layout/base/crashtests/244490-1.html16
-rw-r--r--layout/base/crashtests/254367-1.html6
-rw-r--r--layout/base/crashtests/263359-1.html28
-rw-r--r--layout/base/crashtests/265027-1.html19
-rw-r--r--layout/base/crashtests/265736-1.html2
-rw-r--r--layout/base/crashtests/265736-2.html8
-rw-r--r--layout/base/crashtests/265899-1.html5
-rw-r--r--layout/base/crashtests/265973-1.html8
-rw-r--r--layout/base/crashtests/265986-1.html10
-rw-r--r--layout/base/crashtests/265999-1.html8
-rw-r--r--layout/base/crashtests/266222-1.html7
-rw-r--r--layout/base/crashtests/266360-1.html9
-rw-r--r--layout/base/crashtests/266445-1.html9
-rw-r--r--layout/base/crashtests/266445-2.html9
-rw-r--r--layout/base/crashtests/268157-1.html15
-rw-r--r--layout/base/crashtests/269566-1.html11
-rw-r--r--layout/base/crashtests/272647-1.html18
-rw-r--r--layout/base/crashtests/275746-1.html9
-rw-r--r--layout/base/crashtests/276053-1.html21
-rw-r--r--layout/base/crashtests/280708-1.html9
-rw-r--r--layout/base/crashtests/280708-2.html9
-rw-r--r--layout/base/crashtests/281333-1.html1
-rw-r--r--layout/base/crashtests/285212-1.html13
-rw-r--r--layout/base/crashtests/286813-1.html9
-rw-r--r--layout/base/crashtests/288790-1-inner.xhtml47
-rw-r--r--layout/base/crashtests/288790-1.html9
-rw-r--r--layout/base/crashtests/306940-1.html50
-rw-r--r--layout/base/crashtests/310267-1.xml32
-rw-r--r--layout/base/crashtests/310638-1.svg38
-rw-r--r--layout/base/crashtests/310638-2.html19
-rw-r--r--layout/base/crashtests/311661-1.xul31
-rw-r--r--layout/base/crashtests/311661-2.xul28
-rw-r--r--layout/base/crashtests/313086-1.xml28
-rw-r--r--layout/base/crashtests/317285-1.html1
-rw-r--r--layout/base/crashtests/317934-1-inner.html31
-rw-r--r--layout/base/crashtests/317934-1.html9
-rw-r--r--layout/base/crashtests/320459-1.html7
-rw-r--r--layout/base/crashtests/321058-1.xul4
-rw-r--r--layout/base/crashtests/321058-2.xul25
-rw-r--r--layout/base/crashtests/321077-1.xul6
-rw-r--r--layout/base/crashtests/321077-2.xul22
-rw-r--r--layout/base/crashtests/322436-1.html31
-rw-r--r--layout/base/crashtests/325967-1.html29
-rw-r--r--layout/base/crashtests/325984-1.xhtml5
-rw-r--r--layout/base/crashtests/325984-2.html31
-rw-r--r--layout/base/crashtests/328944-1.xul23
-rw-r--r--layout/base/crashtests/329900-1.html15
-rw-r--r--layout/base/crashtests/330015-1.html14
-rw-r--r--layout/base/crashtests/331204-1.html11
-rw-r--r--layout/base/crashtests/331679-1.xhtml36
-rw-r--r--layout/base/crashtests/331679-2.xml19
-rw-r--r--layout/base/crashtests/331679-3.xml19
-rw-r--r--layout/base/crashtests/331883-1-inner.html30
-rw-r--r--layout/base/crashtests/331883-1.html16
-rw-r--r--layout/base/crashtests/335140-1.html12
-rw-r--r--layout/base/crashtests/336291-1.html19
-rw-r--r--layout/base/crashtests/336999-1.xul26
-rw-r--r--layout/base/crashtests/337066-1.xhtml22
-rw-r--r--layout/base/crashtests/337268-1.html45
-rw-r--r--layout/base/crashtests/337419-1.html23
-rw-r--r--layout/base/crashtests/337476-1.xul32
-rw-r--r--layout/base/crashtests/338703-1.html29
-rw-r--r--layout/base/crashtests/339651-1.html37
-rw-r--r--layout/base/crashtests/340093-1.xul11
-rw-r--r--layout/base/crashtests/341382-1.html22
-rw-r--r--layout/base/crashtests/341382-2.html9
-rw-r--r--layout/base/crashtests/341858-1.html14
-rw-r--r--layout/base/crashtests/342145-1.xhtml26
-rw-r--r--layout/base/crashtests/343293-1.xhtml19
-rw-r--r--layout/base/crashtests/343293-2.xhtml14
-rw-r--r--layout/base/crashtests/343540-1.html26
-rw-r--r--layout/base/crashtests/344057-1.xhtml9
-rw-r--r--layout/base/crashtests/344064-1-inner.xhtml13
-rw-r--r--layout/base/crashtests/344064-1.html9
-rw-r--r--layout/base/crashtests/344300-1-inner.xhtml36
-rw-r--r--layout/base/crashtests/344300-1.html9
-rw-r--r--layout/base/crashtests/344300-2.html10
-rw-r--r--layout/base/crashtests/344340-1.xul28
-rw-r--r--layout/base/crashtests/347898-1.html9
-rw-r--r--layout/base/crashtests/348126-1-inner.html28
-rw-r--r--layout/base/crashtests/348126-1.gifbin0 -> 980 bytes
-rw-r--r--layout/base/crashtests/348126-1.html9
-rw-r--r--layout/base/crashtests/348688-1.html24
-rw-r--r--layout/base/crashtests/348708-1.xhtml20
-rw-r--r--layout/base/crashtests/348729-1-inner.html29
-rw-r--r--layout/base/crashtests/348729-1.html6
-rw-r--r--layout/base/crashtests/349095-1.xhtml25
-rw-r--r--layout/base/crashtests/350128-1.xhtml21
-rw-r--r--layout/base/crashtests/350267-1.html2
-rw-r--r--layout/base/crashtests/354133-1-inner.xhtml22
-rw-r--r--layout/base/crashtests/354133-1.html9
-rw-r--r--layout/base/crashtests/354766-1.xhtml19
-rw-r--r--layout/base/crashtests/354771-1.xul28
-rw-r--r--layout/base/crashtests/355989-1.xhtml27
-rw-r--r--layout/base/crashtests/355993-1.xhtml26
-rw-r--r--layout/base/crashtests/356325-1.xul20
-rw-r--r--layout/base/crashtests/358729-1.xhtml52
-rw-r--r--layout/base/crashtests/360339-1.xul16
-rw-r--r--layout/base/crashtests/360339-2.xul20
-rw-r--r--layout/base/crashtests/363729-1.html3
-rw-r--r--layout/base/crashtests/363729-2.html18
-rw-r--r--layout/base/crashtests/363729-3.html20
-rw-r--r--layout/base/crashtests/364427-1.html34
-rw-r--r--layout/base/crashtests/365909-1.xhtml10
-rw-r--r--layout/base/crashtests/365909-2.xhtml10
-rw-r--r--layout/base/crashtests/366128-1.xhtml32
-rw-r--r--layout/base/crashtests/366271-1-frame.svg13
-rw-r--r--layout/base/crashtests/366271-1.html21
-rw-r--r--layout/base/crashtests/366967-1.html33
-rw-r--r--layout/base/crashtests/367015-1.html22
-rw-r--r--layout/base/crashtests/367243-1.html37
-rw-r--r--layout/base/crashtests/367498-1.html8
-rw-r--r--layout/base/crashtests/367498-2.html14
-rw-r--r--layout/base/crashtests/369176-1.html37
-rw-r--r--layout/base/crashtests/369547-1.html50
-rw-r--r--layout/base/crashtests/369547-2.html15
-rw-r--r--layout/base/crashtests/369945-1.xhtml42
-rw-r--r--layout/base/crashtests/371681-1.xhtml22
-rw-r--r--layout/base/crashtests/372237-1.html33
-rw-r--r--layout/base/crashtests/372475-1.xhtml9
-rw-r--r--layout/base/crashtests/372550-1.html17
-rw-r--r--layout/base/crashtests/372576.xul20
-rw-r--r--layout/base/crashtests/373628-1.html16
-rw-r--r--layout/base/crashtests/373628.html933
-rw-r--r--layout/base/crashtests/373919.xhtml29
-rw-r--r--layout/base/crashtests/374193-1.xhtml7
-rw-r--r--layout/base/crashtests/374193-1xbl.xml10
-rw-r--r--layout/base/crashtests/374297-1.html20
-rw-r--r--layout/base/crashtests/374297-2.html23
-rw-r--r--layout/base/crashtests/376223-1.xhtml29
-rw-r--r--layout/base/crashtests/378325-1.html26
-rw-r--r--layout/base/crashtests/378682.html9
-rw-r--r--layout/base/crashtests/379105-1.xhtml48
-rw-r--r--layout/base/crashtests/379419-1.xhtml12
-rw-r--r--layout/base/crashtests/379768-1.html11
-rw-r--r--layout/base/crashtests/379799-1.html31
-rw-r--r--layout/base/crashtests/379920-1.svg7
-rw-r--r--layout/base/crashtests/379920-2.svg7
-rw-r--r--layout/base/crashtests/379975.html15
-rw-r--r--layout/base/crashtests/380096-1.html4
-rw-r--r--layout/base/crashtests/382204-1.html21
-rw-r--r--layout/base/crashtests/383102-1.xhtml13
-rw-r--r--layout/base/crashtests/383129-1-inner.xhtml22
-rw-r--r--layout/base/crashtests/383129-1.html9
-rw-r--r--layout/base/crashtests/383806-1.xhtml29
-rw-r--r--layout/base/crashtests/384344-1-inner.html20
-rw-r--r--layout/base/crashtests/384344-1.html9
-rw-r--r--layout/base/crashtests/384392-1.xhtml27
-rw-r--r--layout/base/crashtests/384392-2.svg3
-rw-r--r--layout/base/crashtests/384649-1.xhtml31
-rw-r--r--layout/base/crashtests/385354.html18
-rw-r--r--layout/base/crashtests/385866-1.xhtml23
-rw-r--r--layout/base/crashtests/385880-1.xhtml8
-rw-r--r--layout/base/crashtests/386266-1.html28
-rw-r--r--layout/base/crashtests/386476.html12
-rw-r--r--layout/base/crashtests/387195-1.html7
-rw-r--r--layout/base/crashtests/387195-2.xhtml23
-rw-r--r--layout/base/crashtests/388715-1.html22
-rw-r--r--layout/base/crashtests/390976-1.html22
-rw-r--r--layout/base/crashtests/393326-1-binding.xml4
-rw-r--r--layout/base/crashtests/393326-1.html15
-rw-r--r--layout/base/crashtests/393326-2.html15
-rw-r--r--layout/base/crashtests/393661-1.html20
-rw-r--r--layout/base/crashtests/393801-1-inner.html781
-rw-r--r--layout/base/crashtests/393801-1.html7
-rw-r--r--layout/base/crashtests/394014-1-iframe.html21
-rw-r--r--layout/base/crashtests/394014-1-inner.html10
-rw-r--r--layout/base/crashtests/394014-1.html9
-rw-r--r--layout/base/crashtests/394014-2-binding.xml6
-rw-r--r--layout/base/crashtests/394014-2-constructor.xml10
-rw-r--r--layout/base/crashtests/394014-2-constructordestructor.xml12
-rw-r--r--layout/base/crashtests/394014-2-crash.html13
-rw-r--r--layout/base/crashtests/394014-2.html7
-rw-r--r--layout/base/crashtests/394150-1.xhtml27
-rw-r--r--layout/base/crashtests/397011-1.xhtml13
-rw-r--r--layout/base/crashtests/398510-1.xhtml22
-rw-r--r--layout/base/crashtests/398733-1.html20
-rw-r--r--layout/base/crashtests/398733-2.html9
-rw-r--r--layout/base/crashtests/399132-1.xhtml16
-rw-r--r--layout/base/crashtests/399219-1.xhtml17
-rw-r--r--layout/base/crashtests/399365-1.html16
-rw-r--r--layout/base/crashtests/399676-1.xhtml7
-rw-r--r--layout/base/crashtests/399687-1.html38
-rw-r--r--layout/base/crashtests/399940-1.xhtml21
-rw-r--r--layout/base/crashtests/399946-1.xhtml23
-rw-r--r--layout/base/crashtests/399951-1.html14
-rw-r--r--layout/base/crashtests/399994-1.html11
-rw-r--r--layout/base/crashtests/400185-1.xul21
-rw-r--r--layout/base/crashtests/400445-1.xhtml22
-rw-r--r--layout/base/crashtests/400904-1.xhtml20
-rw-r--r--layout/base/crashtests/401589-1.xul29
-rw-r--r--layout/base/crashtests/401734-1.html17
-rw-r--r--layout/base/crashtests/401734-2.html17
-rw-r--r--layout/base/crashtests/403048.html10
-rw-r--r--layout/base/crashtests/403175-1.html30
-rw-r--r--layout/base/crashtests/403245-1.html16
-rw-r--r--layout/base/crashtests/403454.html37
-rw-r--r--layout/base/crashtests/403569-1.xhtml29
-rw-r--r--layout/base/crashtests/403569-2.xhtml19
-rw-r--r--layout/base/crashtests/403569-3.xhtml25
-rw-r--r--layout/base/crashtests/404218-1.xhtml15
-rw-r--r--layout/base/crashtests/404491-1.html5
-rw-r--r--layout/base/crashtests/404721-1.xhtml17
-rw-r--r--layout/base/crashtests/404721-2.xhtml18
-rw-r--r--layout/base/crashtests/405049-1.xul3
-rw-r--r--layout/base/crashtests/405184-1.xhtml31
-rw-r--r--layout/base/crashtests/405186-1.xhtml39
-rw-r--r--layout/base/crashtests/406675-1.html17
-rw-r--r--layout/base/crashtests/408292.html18
-rw-r--r--layout/base/crashtests/408299.html12
-rw-r--r--layout/base/crashtests/408450-1.xhtml7
-rw-r--r--layout/base/crashtests/409461-1.xhtml15
-rw-r--r--layout/base/crashtests/409513.html14
-rw-r--r--layout/base/crashtests/410967.html17
-rw-r--r--layout/base/crashtests/411870-1.html18
-rw-r--r--layout/base/crashtests/412651-1-frame.xhtml29
-rw-r--r--layout/base/crashtests/412651-1.html21
-rw-r--r--layout/base/crashtests/413587-1.svg11
-rw-r--r--layout/base/crashtests/414058-1.html17
-rw-r--r--layout/base/crashtests/414175-1.xul26
-rw-r--r--layout/base/crashtests/415503.xhtml28
-rw-r--r--layout/base/crashtests/416107.xhtml26
-rw-r--r--layout/base/crashtests/419985.html29
-rw-r--r--layout/base/crashtests/420031-1.html8
-rw-r--r--layout/base/crashtests/420213-1.html6
-rw-r--r--layout/base/crashtests/420219-1.html22
-rw-r--r--layout/base/crashtests/420651-1.xhtml4
-rw-r--r--layout/base/crashtests/421203-1.xul5
-rw-r--r--layout/base/crashtests/421432.html14
-rw-r--r--layout/base/crashtests/422276.html18
-rw-r--r--layout/base/crashtests/423107-1.xhtml19
-rw-r--r--layout/base/crashtests/425981-1.html18
-rw-r--r--layout/base/crashtests/428113.xhtml2
-rw-r--r--layout/base/crashtests/428138-1.html24
-rw-r--r--layout/base/crashtests/428448-1.html9
-rw-r--r--layout/base/crashtests/429088-1.html19
-rw-r--r--layout/base/crashtests/429088-2.html25
-rw-r--r--layout/base/crashtests/429780-1.xhtml4
-rw-r--r--layout/base/crashtests/429865-1.html14
-rw-r--r--layout/base/crashtests/429881.html6
-rw-r--r--layout/base/crashtests/430569-1.html3
-rw-r--r--layout/base/crashtests/430569-2.html11
-rw-r--r--layout/base/crashtests/432752-1.svg27
-rw-r--r--layout/base/crashtests/433450-1.html19
-rw-r--r--layout/base/crashtests/436982-1.html7
-rw-r--r--layout/base/crashtests/437142-1.html25
-rw-r--r--layout/base/crashtests/439258-1.html20
-rw-r--r--layout/base/crashtests/439343.html2
-rw-r--r--layout/base/crashtests/444863-1.html25
-rw-r--r--layout/base/crashtests/444925-1.xul10
-rw-r--r--layout/base/crashtests/444967-1.html12
-rw-r--r--layout/base/crashtests/446328-iframe.html1
-rw-r--r--layout/base/crashtests/446328-top.html21
-rw-r--r--layout/base/crashtests/446328.gifbin0 -> 85 bytes
-rw-r--r--layout/base/crashtests/446328.html12
-rw-r--r--layout/base/crashtests/448488-1.html4
-rw-r--r--layout/base/crashtests/448543-1.html8
-rw-r--r--layout/base/crashtests/448543-2.html1
-rw-r--r--layout/base/crashtests/448543-3.html7
-rw-r--r--layout/base/crashtests/450319-1.xhtml32
-rw-r--r--layout/base/crashtests/453894-1.xhtml15
-rw-r--r--layout/base/crashtests/454751-1.xul20
-rw-r--r--layout/base/crashtests/455063-1.html6
-rw-r--r--layout/base/crashtests/455063-2.html6
-rw-r--r--layout/base/crashtests/455063-3.html6
-rw-r--r--layout/base/crashtests/455171-4.html8
-rw-r--r--layout/base/crashtests/455623-1.html19
-rw-r--r--layout/base/crashtests/457362-1.xhtml9
-rw-r--r--layout/base/crashtests/457514.html27
-rw-r--r--layout/base/crashtests/460389-1.html6
-rw-r--r--layout/base/crashtests/46043-1.html12
-rw-r--r--layout/base/crashtests/462392.html43
-rw-r--r--layout/base/crashtests/466763-1.html24
-rw-r--r--layout/base/crashtests/467881-1.html47
-rw-r--r--layout/base/crashtests/468491-1.html16
-rw-r--r--layout/base/crashtests/468546-1.xhtml25
-rw-r--r--layout/base/crashtests/468555-1.xhtml9
-rw-r--r--layout/base/crashtests/468563-1.html7
-rw-r--r--layout/base/crashtests/468578-1.xhtml21
-rw-r--r--layout/base/crashtests/468645-1.xhtml17
-rw-r--r--layout/base/crashtests/468645-2.xhtml13
-rw-r--r--layout/base/crashtests/468645-3.xhtml5
-rw-r--r--layout/base/crashtests/469861-1.xhtml15
-rw-r--r--layout/base/crashtests/469861-2.xhtml15
-rw-r--r--layout/base/crashtests/470851-1.xhtml13
-rw-r--r--layout/base/crashtests/471594-1.xhtml20
-rw-r--r--layout/base/crashtests/473042.xhtml1
-rw-r--r--layout/base/crashtests/474075.html12
-rw-r--r--layout/base/crashtests/477333-1.xhtml22
-rw-r--r--layout/base/crashtests/477731-1.html6
-rw-r--r--layout/base/crashtests/47843-1.html13
-rw-r--r--layout/base/crashtests/479114-1.html14
-rw-r--r--layout/base/crashtests/479360-1.xhtml16
-rw-r--r--layout/base/crashtests/480686-1.html13
-rw-r--r--layout/base/crashtests/481806-1.html14
-rw-r--r--layout/base/crashtests/483604-1.xhtml6
-rw-r--r--layout/base/crashtests/485501-1.html4
-rw-r--r--layout/base/crashtests/487544-1.html2
-rw-r--r--layout/base/crashtests/488390-1.xhtml18
-rw-r--r--layout/base/crashtests/489691.html20
-rw-r--r--layout/base/crashtests/490376-1.xhtml15
-rw-r--r--layout/base/crashtests/490559-1.html16
-rw-r--r--layout/base/crashtests/490747.html8
-rw-r--r--layout/base/crashtests/49122-1.html20
-rw-r--r--layout/base/crashtests/491547-1.xul20
-rw-r--r--layout/base/crashtests/491547-2.xul31
-rw-r--r--layout/base/crashtests/492014.xhtml4
-rw-r--r--layout/base/crashtests/492112-1.xhtml14
-rw-r--r--layout/base/crashtests/492163-1.xhtml21
-rw-r--r--layout/base/crashtests/495350-1.html9
-rw-r--r--layout/base/crashtests/496011-1.xhtml20
-rw-r--r--layout/base/crashtests/497519-1.xhtml28
-rw-r--r--layout/base/crashtests/497519-2.xhtml26
-rw-r--r--layout/base/crashtests/497519-3.xhtml26
-rw-r--r--layout/base/crashtests/497519-4.xhtml26
-rw-r--r--layout/base/crashtests/499741-1.xhtml1
-rw-r--r--layout/base/crashtests/499841-1.xhtml5
-rw-r--r--layout/base/crashtests/499858-1.xhtml5
-rw-r--r--layout/base/crashtests/500467-1.html23141
-rw-r--r--layout/base/crashtests/501878-1.html5
-rw-r--r--layout/base/crashtests/50257-1.html20
-rw-r--r--layout/base/crashtests/503936-1.html29
-rw-r--r--layout/base/crashtests/50395-1.html24
-rw-r--r--layout/base/crashtests/507119.html554
-rw-r--r--layout/base/crashtests/514104-1.xul22
-rw-r--r--layout/base/crashtests/522374-1.html21
-rw-r--r--layout/base/crashtests/522374-2.html21
-rw-r--r--layout/base/crashtests/526378-1.xul28
-rw-r--r--layout/base/crashtests/534367-1.xhtml29
-rw-r--r--layout/base/crashtests/534368-1.xhtml14
-rw-r--r--layout/base/crashtests/534768-1.html23
-rw-r--r--layout/base/crashtests/534768-2.html22
-rw-r--r--layout/base/crashtests/535721-1.xhtml17
-rw-r--r--layout/base/crashtests/535911-1.xhtml16
-rw-r--r--layout/base/crashtests/536623-1.xhtml37
-rw-r--r--layout/base/crashtests/536720.xul23
-rw-r--r--layout/base/crashtests/537059-1.xhtml14
-rw-r--r--layout/base/crashtests/537141-1.xhtml6
-rw-r--r--layout/base/crashtests/537141.xml2
-rw-r--r--layout/base/crashtests/537562-1.xhtml10
-rw-r--r--layout/base/crashtests/537624-1.html18
-rw-r--r--layout/base/crashtests/537631-1.html5
-rw-r--r--layout/base/crashtests/538082-1.xul34
-rw-r--r--layout/base/crashtests/538207-1.xhtml14
-rw-r--r--layout/base/crashtests/538210-1.html16
-rw-r--r--layout/base/crashtests/538267-1.html18
-rw-r--r--layout/base/crashtests/540760.xul18
-rw-r--r--layout/base/crashtests/540771-1.xhtml18
-rw-r--r--layout/base/crashtests/541869-1.xhtml5
-rw-r--r--layout/base/crashtests/541869-2.html5
-rw-r--r--layout/base/crashtests/543648-1.html1
-rw-r--r--layout/base/crashtests/559705.xhtml14
-rw-r--r--layout/base/crashtests/560441-1.xhtml12
-rw-r--r--layout/base/crashtests/560447-1.html1
-rw-r--r--layout/base/crashtests/564063-1.html20
-rw-r--r--layout/base/crashtests/567292-1.xhtml17
-rw-r--r--layout/base/crashtests/56746-1.html16
-rw-r--r--layout/base/crashtests/569018-1.html17
-rw-r--r--layout/base/crashtests/570038-1.html4
-rw-r--r--layout/base/crashtests/572003.xul3
-rw-r--r--layout/base/crashtests/572582-1.xhtml25
-rw-r--r--layout/base/crashtests/576649-1.html4
-rw-r--r--layout/base/crashtests/579655.html26
-rw-r--r--layout/base/crashtests/580129-1.html19
-rw-r--r--layout/base/crashtests/580494-1.html1
-rw-r--r--layout/base/crashtests/580834-1.xhtml5
-rw-r--r--layout/base/crashtests/589787.html27
-rw-r--r--layout/base/crashtests/591075-1.html2
-rw-r--r--layout/base/crashtests/591998-1.html2
-rw-r--r--layout/base/crashtests/595039-1.html1
-rw-r--r--layout/base/crashtests/597924-1.html16
-rw-r--r--layout/base/crashtests/606432-1.html24
-rw-r--r--layout/base/crashtests/609821-1.xhtml17
-rw-r--r--layout/base/crashtests/613817-1.svg12
-rw-r--r--layout/base/crashtests/615146-1.html1
-rw-r--r--layout/base/crashtests/615781-1.xhtml22
-rw-r--r--layout/base/crashtests/616495-single-side-composite-color-border.html21
-rw-r--r--layout/base/crashtests/629035-1.html3
-rw-r--r--layout/base/crashtests/629908-1.html9
-rw-r--r--layout/base/crashtests/635329.html18
-rw-r--r--layout/base/crashtests/636229-1.html2
-rw-r--r--layout/base/crashtests/640272-empty.html0
-rw-r--r--layout/base/crashtests/640272-ref.html14
-rw-r--r--layout/base/crashtests/640272.html15
-rw-r--r--layout/base/crashtests/645193.html15
-rw-r--r--layout/base/crashtests/645572-1.html52
-rw-r--r--layout/base/crashtests/650475.xhtml14
-rw-r--r--layout/base/crashtests/650489.xhtml3
-rw-r--r--layout/base/crashtests/651342-1.html4
-rw-r--r--layout/base/crashtests/653133-1.html17
-rw-r--r--layout/base/crashtests/663295.html2
-rw-r--r--layout/base/crashtests/663662-1.html1
-rw-r--r--layout/base/crashtests/663662-2.html1
-rw-r--r--layout/base/crashtests/665837.html13
-rw-r--r--layout/base/crashtests/668579.html10
-rw-r--r--layout/base/crashtests/668941.xhtml16
-rw-r--r--layout/base/crashtests/670226.html10
-rw-r--r--layout/base/crashtests/675246-1.xhtml8
-rw-r--r--layout/base/crashtests/690247-1.html2
-rw-r--r--layout/base/crashtests/690619-1.html1
-rw-r--r--layout/base/crashtests/691118-1.html24
-rw-r--r--layout/base/crashtests/695861.html9
-rw-r--r--layout/base/crashtests/695964-1.svg1
-rw-r--r--layout/base/crashtests/698335.html2
-rw-r--r--layout/base/crashtests/699353-1.html18
-rw-r--r--layout/base/crashtests/701504.html24
-rw-r--r--layout/base/crashtests/707098.html6
-rw-r--r--layout/base/crashtests/709536-1.xhtml1
-rw-r--r--layout/base/crashtests/722137.html18
-rw-r--r--layout/base/crashtests/725535.html8
-rw-r--r--layout/base/crashtests/727601.html3
-rw-r--r--layout/base/crashtests/735943.html38
-rw-r--r--layout/base/crashtests/736389-1.xhtml47
-rw-r--r--layout/base/crashtests/736924-1.html23
-rw-r--r--layout/base/crashtests/749816-1.html15
-rw-r--r--layout/base/crashtests/763223-1.html6
-rw-r--r--layout/base/crashtests/763702.xhtml9
-rw-r--r--layout/base/crashtests/767593-1.html7
-rw-r--r--layout/base/crashtests/767593-2.html7
-rw-r--r--layout/base/crashtests/770381-1.html12
-rw-r--r--layout/base/crashtests/772306.html40
-rw-r--r--layout/base/crashtests/788360.html6
-rw-r--r--layout/base/crashtests/793848.html28
-rw-r--r--layout/base/crashtests/795646.html7
-rw-r--r--layout/base/crashtests/802902.html10
-rw-r--r--layout/base/crashtests/806056-1.html16
-rw-r--r--layout/base/crashtests/806056-2.html18
-rw-r--r--layout/base/crashtests/812665.html6
-rw-r--r--layout/base/crashtests/813372-1.html52
-rw-r--r--layout/base/crashtests/817219-iframe.html35
-rw-r--r--layout/base/crashtests/817219.html22
-rw-r--r--layout/base/crashtests/818454.html24
-rw-r--r--layout/base/crashtests/822865.html4
-rw-r--r--layout/base/crashtests/824862.html5
-rw-r--r--layout/base/crashtests/826163.html11
-rw-r--r--layout/base/crashtests/830138-1.html17
-rw-r--r--layout/base/crashtests/830192-1.html31
-rw-r--r--layout/base/crashtests/830299-1.html27
-rw-r--r--layout/base/crashtests/833604-1.html18
-rw-r--r--layout/base/crashtests/835056.html19
-rw-r--r--layout/base/crashtests/836990-1.html12
-rw-r--r--layout/base/crashtests/840480.html44
-rw-r--r--layout/base/crashtests/847242.html13
-rw-r--r--layout/base/crashtests/852293.html67
-rw-r--r--layout/base/crashtests/859526-1.html7
-rw-r--r--layout/base/crashtests/859630-1.html7
-rw-r--r--layout/base/crashtests/860579-1.html21
-rw-r--r--layout/base/crashtests/866588.html25
-rw-r--r--layout/base/crashtests/876092.html29
-rw-r--r--layout/base/crashtests/876221.html39
-rw-r--r--layout/base/crashtests/89101-1.html22
-rw-r--r--layout/base/crashtests/89358-1.html10
-rw-r--r--layout/base/crashtests/897852.html9
-rw-r--r--layout/base/crashtests/898913.html24
-rw-r--r--layout/base/crashtests/90205-1.html15
-rw-r--r--layout/base/crashtests/919434.html5
-rw-r--r--layout/base/crashtests/926728.html13
-rw-r--r--layout/base/crashtests/930381.html122
-rw-r--r--layout/base/crashtests/931450.html10
-rw-r--r--layout/base/crashtests/931460-1.html5
-rw-r--r--layout/base/crashtests/931464.html18
-rw-r--r--layout/base/crashtests/935765-1.html9
-rw-r--r--layout/base/crashtests/936988-1.html9
-rw-r--r--layout/base/crashtests/942690.html15
-rw-r--r--layout/base/crashtests/973390-1.html7
-rw-r--r--layout/base/crashtests/99776-1.html9
-rw-r--r--layout/base/crashtests/crashtests.list485
-rw-r--r--layout/base/doc/AccessibleCaretEventHubStates.dot42
-rw-r--r--layout/base/doc/AccessibleCaretEventHubStates.pngbin0 -> 97614 bytes
-rw-r--r--layout/base/gtest/TestAccessibleCaretEventHub.cpp827
-rw-r--r--layout/base/gtest/TestAccessibleCaretManager.cpp810
-rw-r--r--layout/base/gtest/moz.build29
-rw-r--r--layout/base/moz.build233
-rw-r--r--layout/base/nsArenaMemoryStats.h95
-rw-r--r--layout/base/nsAutoLayoutPhase.cpp74
-rw-r--r--layout/base/nsAutoLayoutPhase.h49
-rw-r--r--layout/base/nsBidi.h16
-rw-r--r--layout/base/nsBidiPresUtils.cpp2297
-rw-r--r--layout/base/nsBidiPresUtils.h546
-rw-r--r--layout/base/nsBidi_ICU.cpp68
-rw-r--r--layout/base/nsBidi_ICU.h190
-rw-r--r--layout/base/nsBidi_noICU.cpp2089
-rw-r--r--layout/base/nsBidi_noICU.h709
-rw-r--r--layout/base/nsCSSColorUtils.cpp259
-rw-r--r--layout/base/nsCSSColorUtils.h41
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp12907
-rw-r--r--layout/base/nsCSSFrameConstructor.h2136
-rw-r--r--layout/base/nsCSSRendering.cpp6138
-rw-r--r--layout/base/nsCSSRendering.h1030
-rw-r--r--layout/base/nsCSSRenderingBorders.cpp3532
-rw-r--r--layout/base/nsCSSRenderingBorders.h312
-rw-r--r--layout/base/nsCaret.cpp968
-rw-r--r--layout/base/nsCaret.h264
-rw-r--r--layout/base/nsChangeHint.h540
-rw-r--r--layout/base/nsCompatibility.h17
-rw-r--r--layout/base/nsCounterManager.cpp346
-rw-r--r--layout/base/nsCounterManager.h281
-rw-r--r--layout/base/nsDisplayItemTypes.h72
-rw-r--r--layout/base/nsDisplayItemTypesList.h100
-rw-r--r--layout/base/nsDisplayList.cpp7553
-rw-r--r--layout/base/nsDisplayList.h4550
-rw-r--r--layout/base/nsDisplayListInvalidation.cpp153
-rw-r--r--layout/base/nsDisplayListInvalidation.h322
-rw-r--r--layout/base/nsDocumentViewer.cpp4668
-rw-r--r--layout/base/nsFrameManager.cpp835
-rw-r--r--layout/base/nsFrameManager.h222
-rw-r--r--layout/base/nsFrameManagerBase.h72
-rw-r--r--layout/base/nsFrameTraversal.cpp541
-rw-r--r--layout/base/nsFrameTraversal.h44
-rw-r--r--layout/base/nsGenConList.cpp192
-rw-r--r--layout/base/nsGenConList.h132
-rw-r--r--layout/base/nsIDocumentViewerPrint.h86
-rw-r--r--layout/base/nsIFrameTraversal.h75
-rw-r--r--layout/base/nsILayoutDebugger.h39
-rw-r--r--layout/base/nsILayoutHistoryState.h70
-rw-r--r--layout/base/nsIPercentBSizeObserver.h33
-rw-r--r--layout/base/nsIPresShell.h1868
-rw-r--r--layout/base/nsIReflowCallback.h33
-rw-r--r--layout/base/nsIStyleSheetService.idl62
-rw-r--r--layout/base/nsLayoutDebugger.cpp315
-rw-r--r--layout/base/nsLayoutHistoryState.cpp108
-rw-r--r--layout/base/nsLayoutUtils.cpp9186
-rw-r--r--layout/base/nsLayoutUtils.h3041
-rw-r--r--layout/base/nsPresArena.cpp247
-rw-r--r--layout/base/nsPresArena.h163
-rw-r--r--layout/base/nsPresArenaObjectList.h72
-rw-r--r--layout/base/nsPresContext.cpp3159
-rw-r--r--layout/base/nsPresContext.h1588
-rw-r--r--layout/base/nsPresShell.cpp11280
-rw-r--r--layout/base/nsPresShell.h966
-rw-r--r--layout/base/nsPresState.h126
-rw-r--r--layout/base/nsQuoteList.cpp118
-rw-r--r--layout/base/nsQuoteList.h92
-rw-r--r--layout/base/nsRefreshDriver.cpp2373
-rw-r--r--layout/base/nsRefreshDriver.h523
-rw-r--r--layout/base/nsStyleChangeList.cpp52
-rw-r--r--layout/base/nsStyleChangeList.h45
-rw-r--r--layout/base/nsStyleSheetService.cpp405
-rw-r--r--layout/base/nsStyleSheetService.h81
-rw-r--r--layout/base/tests/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--layout/base/tests/border_radius_hit_testing_iframe.html27
-rw-r--r--layout/base/tests/browser.ini6
-rw-r--r--layout/base/tests/browser_bug617076.js46
-rw-r--r--layout/base/tests/browser_disableDialogs_onbeforeunload.js56
-rw-r--r--layout/base/tests/browser_onbeforeunload_only_after_interaction.js53
-rw-r--r--layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js59
-rw-r--r--layout/base/tests/bug1007065-1-ref.html15
-rw-r--r--layout/base/tests/bug1007065-1.html15
-rw-r--r--layout/base/tests/bug1007067-1-ref.html20
-rw-r--r--layout/base/tests/bug1007067-1.html20
-rw-r--r--layout/base/tests/bug1061468-ref.html13
-rw-r--r--layout/base/tests/bug1061468.html40
-rw-r--r--layout/base/tests/bug106855-1-ref.html27
-rw-r--r--layout/base/tests/bug106855-1.html25
-rw-r--r--layout/base/tests/bug106855-2.html26
-rw-r--r--layout/base/tests/bug1078327_inner.html107
-rw-r--r--layout/base/tests/bug1080360_inner.html83
-rw-r--r--layout/base/tests/bug1080361_inner.html113
-rw-r--r--layout/base/tests/bug1082486-1-ref.html18
-rw-r--r--layout/base/tests/bug1082486-1.html27
-rw-r--r--layout/base/tests/bug1082486-2-ref.html12
-rw-r--r--layout/base/tests/bug1082486-2.html12
-rw-r--r--layout/base/tests/bug1093686_inner.html84
-rw-r--r--layout/base/tests/bug1097242-1-ref.html14
-rw-r--r--layout/base/tests/bug1097242-1.html18
-rw-r--r--layout/base/tests/bug1109968-1-ref.html17
-rw-r--r--layout/base/tests/bug1109968-1.html23
-rw-r--r--layout/base/tests/bug1109968-2-ref.html17
-rw-r--r--layout/base/tests/bug1109968-2.html23
-rw-r--r--layout/base/tests/bug1123067-1.html38
-rw-r--r--layout/base/tests/bug1123067-2.html34
-rw-r--r--layout/base/tests/bug1123067-3.html35
-rw-r--r--layout/base/tests/bug1123067-ref.html33
-rw-r--r--layout/base/tests/bug1132768-1-ref.html12
-rw-r--r--layout/base/tests/bug1132768-1.html17
-rw-r--r--layout/base/tests/bug1153130_inner.html72
-rw-r--r--layout/base/tests/bug1162990_inner_1.html145
-rw-r--r--layout/base/tests/bug1162990_inner_2.html146
-rw-r--r--layout/base/tests/bug1226904.html35
-rw-r--r--layout/base/tests/bug1237236-1-ref.html30
-rw-r--r--layout/base/tests/bug1237236-1.html31
-rw-r--r--layout/base/tests/bug1237236-2-ref.html32
-rw-r--r--layout/base/tests/bug1237236-2.html30
-rw-r--r--layout/base/tests/bug1258308-1-ref.html32
-rw-r--r--layout/base/tests/bug1258308-1.html40
-rw-r--r--layout/base/tests/bug1258308-2-ref.html37
-rw-r--r--layout/base/tests/bug1258308-2.html31
-rw-r--r--layout/base/tests/bug1259949-1-ref.html30
-rw-r--r--layout/base/tests/bug1259949-1.html34
-rw-r--r--layout/base/tests/bug1259949-2-ref.html36
-rw-r--r--layout/base/tests/bug1259949-2.html30
-rw-r--r--layout/base/tests/bug1263288-ref.html28
-rw-r--r--layout/base/tests/bug1263288.html30
-rw-r--r--layout/base/tests/bug1263357-1-ref.html28
-rw-r--r--layout/base/tests/bug1263357-1.html34
-rw-r--r--layout/base/tests/bug1263357-2-ref.html28
-rw-r--r--layout/base/tests/bug1263357-2.html34
-rw-r--r--layout/base/tests/bug1263357-3-ref.html27
-rw-r--r--layout/base/tests/bug1263357-3.html28
-rw-r--r--layout/base/tests/bug1263357-4-ref.html27
-rw-r--r--layout/base/tests/bug1263357-4.html28
-rw-r--r--layout/base/tests/bug1263357-5-ref.html27
-rw-r--r--layout/base/tests/bug1263357-5.html28
-rw-r--r--layout/base/tests/bug240933-1-ref.html12
-rw-r--r--layout/base/tests/bug240933-1.html13
-rw-r--r--layout/base/tests/bug240933-2.html15
-rw-r--r--layout/base/tests/bug369950-subframe.xml11
-rw-r--r--layout/base/tests/bug389321-1-ref.html17
-rw-r--r--layout/base/tests/bug389321-1.html19
-rw-r--r--layout/base/tests/bug389321-2-ref.html9
-rw-r--r--layout/base/tests/bug389321-2.html9
-rw-r--r--layout/base/tests/bug389321-3-ref.html9
-rw-r--r--layout/base/tests/bug389321-3.html9
-rw-r--r--layout/base/tests/bug450930.xhtml181
-rw-r--r--layout/base/tests/bug482484-ref.html18
-rw-r--r--layout/base/tests/bug482484.html22
-rw-r--r--layout/base/tests/bug503399-ref.html44
-rw-r--r--layout/base/tests/bug503399.html43
-rw-r--r--layout/base/tests/bug512295-1-ref.html28
-rw-r--r--layout/base/tests/bug512295-1.html34
-rw-r--r--layout/base/tests/bug512295-2-ref.html28
-rw-r--r--layout/base/tests/bug512295-2.html34
-rw-r--r--layout/base/tests/bug558663.html101
-rw-r--r--layout/base/tests/bug583889_inner1.html67
-rw-r--r--layout/base/tests/bug583889_inner2.html5
-rw-r--r--layout/base/tests/bug585922-ref.html21
-rw-r--r--layout/base/tests/bug585922.html35
-rw-r--r--layout/base/tests/bug597519-1-ref.html12
-rw-r--r--layout/base/tests/bug597519-1.html16
-rw-r--r--layout/base/tests/bug602141-1-ref.html18
-rw-r--r--layout/base/tests/bug602141-1.html21
-rw-r--r--layout/base/tests/bug602141-2-ref.html18
-rw-r--r--layout/base/tests/bug602141-2.html23
-rw-r--r--layout/base/tests/bug602141-3-ref.html18
-rw-r--r--layout/base/tests/bug602141-3.html21
-rw-r--r--layout/base/tests/bug602141-4-ref.html18
-rw-r--r--layout/base/tests/bug602141-4.html21
-rw-r--r--layout/base/tests/bug612271-1.html15
-rw-r--r--layout/base/tests/bug612271-2.html15
-rw-r--r--layout/base/tests/bug612271-3.html15
-rw-r--r--layout/base/tests/bug612271-ref.html17
-rw-r--r--layout/base/tests/bug613433-1.html24
-rw-r--r--layout/base/tests/bug613433-2.html24
-rw-r--r--layout/base/tests/bug613433-3.html24
-rw-r--r--layout/base/tests/bug613433-ref.html21
-rw-r--r--layout/base/tests/bug613807-1-ref.html6
-rw-r--r--layout/base/tests/bug613807-1.html90
-rw-r--r--layout/base/tests/bug632215-1.html29
-rw-r--r--layout/base/tests/bug632215-2.html28
-rw-r--r--layout/base/tests/bug632215-ref.html17
-rw-r--r--layout/base/tests/bug633044-1-ref.html16
-rw-r--r--layout/base/tests/bug633044-1.html24
-rw-r--r--layout/base/tests/bug634406-1-ref.html10
-rw-r--r--layout/base/tests/bug634406-1.html16
-rw-r--r--layout/base/tests/bug644428-1-ref.html17
-rw-r--r--layout/base/tests/bug644428-1.html19
-rw-r--r--layout/base/tests/bug646382-1-ref.html17
-rw-r--r--layout/base/tests/bug646382-1.html22
-rw-r--r--layout/base/tests/bug646382-2-ref.html14
-rw-r--r--layout/base/tests/bug646382-2.html21
-rw-r--r--layout/base/tests/bug664087-1-ref.html21
-rw-r--r--layout/base/tests/bug664087-1.html25
-rw-r--r--layout/base/tests/bug664087-2-ref.html21
-rw-r--r--layout/base/tests/bug664087-2.html25
-rw-r--r--layout/base/tests/bug682712-1-ref.html24
-rw-r--r--layout/base/tests/bug682712-1.html32
-rw-r--r--layout/base/tests/bug687297_a.html17
-rw-r--r--layout/base/tests/bug687297_b.html17
-rw-r--r--layout/base/tests/bug687297_c.html17
-rw-r--r--layout/base/tests/bug746993-1-ref.html20
-rw-r--r--layout/base/tests/bug746993-1.html22
-rw-r--r--layout/base/tests/bug851445_helper.html11
-rw-r--r--layout/base/tests/bug921928_event_target_iframe_apps_oop.html8
-rw-r--r--layout/base/tests/bug923376-ref.html11
-rw-r--r--layout/base/tests/bug923376.html15
-rw-r--r--layout/base/tests/bug956530-1-ref.html27
-rw-r--r--layout/base/tests/bug956530-1.html35
-rw-r--r--layout/base/tests/bug966992-1-ref.html40
-rw-r--r--layout/base/tests/bug966992-1.html36
-rw-r--r--layout/base/tests/bug966992-2-ref.html42
-rw-r--r--layout/base/tests/bug966992-2.html38
-rw-r--r--layout/base/tests/bug966992-3-ref.html28
-rw-r--r--layout/base/tests/bug966992-3.html26
-rw-r--r--layout/base/tests/bug968148_inner.html295
-rw-r--r--layout/base/tests/bug970964_inner.html342
-rw-r--r--layout/base/tests/bug976963_inner.html241
-rw-r--r--layout/base/tests/bug977003_inner_1.html100
-rw-r--r--layout/base/tests/bug977003_inner_2.html75
-rw-r--r--layout/base/tests/bug977003_inner_3.html95
-rw-r--r--layout/base/tests/bug977003_inner_4.html100
-rw-r--r--layout/base/tests/bug977003_inner_5.html115
-rw-r--r--layout/base/tests/bug977003_inner_6.html101
-rw-r--r--layout/base/tests/bug989012-1-ref.html21
-rw-r--r--layout/base/tests/bug989012-1.html24
-rw-r--r--layout/base/tests/bug989012-2-ref.html26
-rw-r--r--layout/base/tests/bug989012-2.html29
-rw-r--r--layout/base/tests/bug989012-3-ref.html28
-rw-r--r--layout/base/tests/bug989012-3.html31
-rw-r--r--layout/base/tests/chrome/animated.gifbin0 -> 527 bytes
-rw-r--r--layout/base/tests/chrome/blue-32x32.pngbin0 -> 110 bytes
-rw-r--r--layout/base/tests/chrome/bug1041200_window.html45
-rw-r--r--layout/base/tests/chrome/bug495648.rdf214
-rw-r--r--layout/base/tests/chrome/bug551434_childframe.html4
-rw-r--r--layout/base/tests/chrome/chrome.ini49
-rw-r--r--layout/base/tests/chrome/chrome_content_integration_window.xul45
-rw-r--r--layout/base/tests/chrome/chrome_over_plugin_window.xul62
-rw-r--r--layout/base/tests/chrome/default_background_window.xul60
-rw-r--r--layout/base/tests/chrome/dialog_with_positioning_window.xul30
-rw-r--r--layout/base/tests/chrome/file_bug1018265.xul51
-rw-r--r--layout/base/tests/chrome/no_clip_iframe_subdoc.html7
-rw-r--r--layout/base/tests/chrome/no_clip_iframe_window.xul96
-rw-r--r--layout/base/tests/chrome/printpreview_bug396024_helper.xul123
-rw-r--r--layout/base/tests/chrome/printpreview_bug482976_helper.xul82
-rw-r--r--layout/base/tests/chrome/printpreview_helper.xul274
-rw-r--r--layout/base/tests/chrome/test_bug1018265.xul38
-rw-r--r--layout/base/tests/chrome/test_bug1041200.xul23
-rw-r--r--layout/base/tests/chrome/test_bug396367-1.html49
-rw-r--r--layout/base/tests/chrome/test_bug396367-2.html56
-rw-r--r--layout/base/tests/chrome/test_bug420499.xul129
-rw-r--r--layout/base/tests/chrome/test_bug458898.html39
-rw-r--r--layout/base/tests/chrome/test_bug495648.xul46
-rw-r--r--layout/base/tests/chrome/test_bug504311.xul35
-rw-r--r--layout/base/tests/chrome/test_bug514660.xul40
-rw-r--r--layout/base/tests/chrome/test_bug533845.xul44
-rw-r--r--layout/base/tests/chrome/test_bug551434.html97
-rw-r--r--layout/base/tests/chrome/test_bug708062.html47
-rw-r--r--layout/base/tests/chrome/test_bug812817.xul38
-rw-r--r--layout/base/tests/chrome/test_bug847890_paintFlashing.html31
-rw-r--r--layout/base/tests/chrome/test_chrome_content_integration.xul26
-rw-r--r--layout/base/tests/chrome/test_chrome_over_plugin.xul24
-rw-r--r--layout/base/tests/chrome/test_default_background.xul23
-rw-r--r--layout/base/tests/chrome/test_dialog_with_positioning.html20
-rw-r--r--layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html41
-rw-r--r--layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul114
-rw-r--r--layout/base/tests/chrome/test_no_clip_iframe.xul23
-rw-r--r--layout/base/tests/chrome/test_prerendered_transforms.html47
-rw-r--r--layout/base/tests/chrome/test_printpreview.xul16
-rw-r--r--layout/base/tests/chrome/test_printpreview_bug396024.xul22
-rw-r--r--layout/base/tests/chrome/test_printpreview_bug482976.xul22
-rw-r--r--layout/base/tests/chrome/test_scrolling_repaints.html49
-rw-r--r--layout/base/tests/chrome/test_will_change.html99
-rw-r--r--layout/base/tests/file_bug607529.html40
-rw-r--r--layout/base/tests/file_bug842853.html13
-rw-r--r--layout/base/tests/file_bug842853.sjs14
-rw-r--r--layout/base/tests/image_rgrg-256x256.pngbin0 -> 131 bytes
-rw-r--r--layout/base/tests/image_rrgg-256x256.pngbin0 -> 120 bytes
-rw-r--r--layout/base/tests/input-invalid-ref.html7
-rw-r--r--layout/base/tests/input-maxlength-invalid-change.html25
-rw-r--r--layout/base/tests/input-maxlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/input-maxlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/input-maxlength-valid-before-change.html15
-rw-r--r--layout/base/tests/input-maxlength-valid-change.html28
-rw-r--r--layout/base/tests/input-minlength-invalid-change.html25
-rw-r--r--layout/base/tests/input-minlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/input-minlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/input-minlength-valid-before-change.html15
-rw-r--r--layout/base/tests/input-minlength-valid-change.html28
-rw-r--r--layout/base/tests/input-ui-valid-ref.html6
-rw-r--r--layout/base/tests/input-valid-ref.html7
-rw-r--r--layout/base/tests/marionette/manifest.ini5
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py298
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_selection_mode.py632
-rw-r--r--layout/base/tests/mochitest.ini316
-rw-r--r--layout/base/tests/multi-range-script-select-ref.html173
-rw-r--r--layout/base/tests/multi-range-script-select.html185
-rw-r--r--layout/base/tests/multi-range-user-select-ref.html166
-rw-r--r--layout/base/tests/multi-range-user-select.html223
-rw-r--r--layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html97
-rw-r--r--layout/base/tests/preserve3d_sorting_hit_testing_iframe.html32
-rw-r--r--layout/base/tests/resize_flush_iframe.html17
-rw-r--r--layout/base/tests/scroll_selection_into_view_window.html66
-rw-r--r--layout/base/tests/selection-utils.js154
-rw-r--r--layout/base/tests/test_after_paint_pref.html111
-rw-r--r--layout/base/tests/test_border_radius_hit_testing.html106
-rw-r--r--layout/base/tests/test_bug1078327.html37
-rw-r--r--layout/base/tests/test_bug1080360.html37
-rw-r--r--layout/base/tests/test_bug1080361.html38
-rw-r--r--layout/base/tests/test_bug1093686.html41
-rw-r--r--layout/base/tests/test_bug1120705.html85
-rw-r--r--layout/base/tests/test_bug114649.html67
-rw-r--r--layout/base/tests/test_bug1153130.html37
-rw-r--r--layout/base/tests/test_bug1162990.html39
-rw-r--r--layout/base/tests/test_bug1226904.html44
-rw-r--r--layout/base/tests/test_bug1246622.html45
-rw-r--r--layout/base/tests/test_bug1278021.html45
-rw-r--r--layout/base/tests/test_bug332655-1.html60
-rw-r--r--layout/base/tests/test_bug332655-2.html79
-rw-r--r--layout/base/tests/test_bug369950.html91
-rw-r--r--layout/base/tests/test_bug370436.html93
-rw-r--r--layout/base/tests/test_bug386575.xhtml46
-rw-r--r--layout/base/tests/test_bug388019.html44
-rw-r--r--layout/base/tests/test_bug394057.html88
-rw-r--r--layout/base/tests/test_bug399284.html116
-rw-r--r--layout/base/tests/test_bug399951.html34
-rw-r--r--layout/base/tests/test_bug404209.xhtml47
-rw-r--r--layout/base/tests/test_bug416896.html65
-rw-r--r--layout/base/tests/test_bug423523.html104
-rw-r--r--layout/base/tests/test_bug435293-interaction.html49
-rw-r--r--layout/base/tests/test_bug435293-scale.html103
-rw-r--r--layout/base/tests/test_bug435293-skew.html173
-rw-r--r--layout/base/tests/test_bug449781.html68
-rw-r--r--layout/base/tests/test_bug450930.xhtml28
-rw-r--r--layout/base/tests/test_bug465448.xul45
-rw-r--r--layout/base/tests/test_bug469170.html49
-rw-r--r--layout/base/tests/test_bug471126.html34
-rw-r--r--layout/base/tests/test_bug499538-1.html60
-rw-r--r--layout/base/tests/test_bug514127.html55
-rw-r--r--layout/base/tests/test_bug518777.html48
-rw-r--r--layout/base/tests/test_bug548545.xhtml47
-rw-r--r--layout/base/tests/test_bug558663.html37
-rw-r--r--layout/base/tests/test_bug559499.html26
-rw-r--r--layout/base/tests/test_bug569520.html67
-rw-r--r--layout/base/tests/test_bug582181-1.html60
-rw-r--r--layout/base/tests/test_bug582181-2.html63
-rw-r--r--layout/base/tests/test_bug582771.html128
-rw-r--r--layout/base/tests/test_bug583889.html55
-rw-r--r--layout/base/tests/test_bug588174.html67
-rw-r--r--layout/base/tests/test_bug603550.html113
-rw-r--r--layout/base/tests/test_bug607529.html64
-rw-r--r--layout/base/tests/test_bug629838.html97
-rw-r--r--layout/base/tests/test_bug644768.html62
-rw-r--r--layout/base/tests/test_bug646757.html43
-rw-r--r--layout/base/tests/test_bug66619.html62
-rw-r--r--layout/base/tests/test_bug667512.html41
-rw-r--r--layout/base/tests/test_bug677878.html54
-rw-r--r--layout/base/tests/test_bug687297.html54
-rw-r--r--layout/base/tests/test_bug696020.html47
-rw-r--r--layout/base/tests/test_bug718809.html28
-rw-r--r--layout/base/tests/test_bug725426.html23
-rw-r--r--layout/base/tests/test_bug731777.html49
-rw-r--r--layout/base/tests/test_bug749186.html41
-rw-r--r--layout/base/tests/test_bug761572.html40
-rw-r--r--layout/base/tests/test_bug770106.html24
-rw-r--r--layout/base/tests/test_bug842853-2.html54
-rw-r--r--layout/base/tests/test_bug842853.html50
-rw-r--r--layout/base/tests/test_bug849219.html50
-rw-r--r--layout/base/tests/test_bug851445.html34
-rw-r--r--layout/base/tests/test_bug851485.html76
-rw-r--r--layout/base/tests/test_bug858459.html59
-rw-r--r--layout/base/tests/test_bug93077-1.html32
-rw-r--r--layout/base/tests/test_bug93077-2.html32
-rw-r--r--layout/base/tests/test_bug93077-3.html35
-rw-r--r--layout/base/tests/test_bug93077-4.html35
-rw-r--r--layout/base/tests/test_bug93077-5.html35
-rw-r--r--layout/base/tests/test_bug93077-6.html35
-rw-r--r--layout/base/tests/test_bug968148.html36
-rw-r--r--layout/base/tests/test_bug970964.html36
-rw-r--r--layout/base/tests/test_bug976963.html35
-rw-r--r--layout/base/tests/test_bug977003.html39
-rw-r--r--layout/base/tests/test_bug990340.html60
-rw-r--r--layout/base/tests/test_bug993936.html161
-rw-r--r--layout/base/tests/test_emulateMedium.html141
-rw-r--r--layout/base/tests/test_event_target_iframe_oop.html178
-rw-r--r--layout/base/tests/test_event_target_radius.html293
-rw-r--r--layout/base/tests/test_flush_on_paint.html64
-rw-r--r--layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html74
-rw-r--r--layout/base/tests/test_frame_reconstruction_scroll_restore.html68
-rw-r--r--layout/base/tests/test_getBoxQuads_convertPointRectQuad.html713
-rw-r--r--layout/base/tests/test_getClientRects_emptytext.html26
-rw-r--r--layout/base/tests/test_mozPaintCount.html72
-rw-r--r--layout/base/tests/test_preserve3d_sorting_hit_testing.html48
-rw-r--r--layout/base/tests/test_preserve3d_sorting_hit_testing2.html40
-rw-r--r--layout/base/tests/test_reftests_with_caret.html341
-rw-r--r--layout/base/tests/test_remote_frame.html66
-rw-r--r--layout/base/tests/test_resize_flush.html51
-rw-r--r--layout/base/tests/test_scroll_event_ordering.html63
-rw-r--r--layout/base/tests/test_scroll_selection_into_view.html99
-rw-r--r--layout/base/tests/test_scroll_snapping.html806
-rw-r--r--layout/base/tests/test_scroll_snapping_scrollbars.html349
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints.html54
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints_2.html54
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints_3.html24
-rw-r--r--layout/base/tests/textarea-invalid-ref.html7
-rw-r--r--layout/base/tests/textarea-maxlength-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-maxlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-maxlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/textarea-maxlength-valid-before-change.html15
-rw-r--r--layout/base/tests/textarea-maxlength-valid-change.html28
-rw-r--r--layout/base/tests/textarea-minlength-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-minlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-minlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/textarea-minlength-valid-before-change.html15
-rw-r--r--layout/base/tests/textarea-minlength-valid-change.html28
-rw-r--r--layout/base/tests/textarea-valid-ref.html7
-rw-r--r--layout/base/tests/transformed_scrolling_repaints_3_window.html115
1008 files changed, 175676 insertions, 0 deletions
diff --git a/layout/base/AccessibleCaret.cpp b/layout/base/AccessibleCaret.cpp
new file mode 100644
index 000000000..165f385f5
--- /dev/null
+++ b/layout/base/AccessibleCaret.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/FloatingPoint.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ToString.h"
+#include "nsCanvasFrame.h"
+#include "nsCaret.h"
+#include "nsDOMTokenList.h"
+#include "nsIFrame.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)
+
+float AccessibleCaret::sWidth = 0.0f;
+float AccessibleCaret::sHeight = 0.0f;
+float AccessibleCaret::sMarginLeft = 0.0f;
+float AccessibleCaret::sBarWidth = 0.0f;
+
+NS_NAMED_LITERAL_STRING(AccessibleCaret::sTextOverlayElementId, "text-overlay");
+NS_NAMED_LITERAL_STRING(AccessibleCaret::sCaretImageElementId, "image");
+NS_NAMED_LITERAL_STRING(AccessibleCaret::sSelectionBarElementId, "bar");
+
+#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::Changed);
+ AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
+ }
+ return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
+// -----------------------------------------------------------------------------
+// Implementation of AccessibleCaret methods
+
+AccessibleCaret::AccessibleCaret(nsIPresShell* aPresShell)
+ : mPresShell(aPresShell)
+{
+ // Check all resources required.
+ if (mPresShell) {
+ MOZ_ASSERT(RootFrame());
+ MOZ_ASSERT(mPresShell->GetDocument());
+ MOZ_ASSERT(mPresShell->GetCanvasFrame());
+ MOZ_ASSERT(mPresShell->GetCanvasFrame()->GetCustomContentContainer());
+
+ InjectCaretElement(mPresShell->GetDocument());
+ }
+
+ static bool prefsAdded = false;
+ if (!prefsAdded) {
+ Preferences::AddFloatVarCache(&sWidth, "layout.accessiblecaret.width");
+ Preferences::AddFloatVarCache(&sHeight, "layout.accessiblecaret.height");
+ Preferences::AddFloatVarCache(&sMarginLeft, "layout.accessiblecaret.margin-left");
+ Preferences::AddFloatVarCache(&sBarWidth, "layout.accessiblecaret.bar.width");
+ prefsAdded = true;
+ }
+}
+
+AccessibleCaret::~AccessibleCaret()
+{
+ if (mPresShell) {
+ RemoveCaretElement(mPresShell->GetDocument());
+ }
+}
+
+void
+AccessibleCaret::SetAppearance(Appearance aAppearance)
+{
+ if (mAppearance == aAppearance) {
+ return;
+ }
+
+ ErrorResult 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) {
+ mImaginaryCaretRect = nsRect();
+ mZoomLevel = 0.0f;
+ }
+}
+
+void
+AccessibleCaret::SetSelectionBarEnabled(bool aEnabled)
+{
+ if (mSelectionBarEnabled == aEnabled) {
+ return;
+ }
+
+ AC_LOG("Set selection bar %s", aEnabled ? "Enabled" : "Disabled");
+
+ ErrorResult rv;
+ CaretElement()->ClassList()->Toggle(NS_LITERAL_STRING("no-bar"),
+ Optional<bool>(!aEnabled), rv);
+ MOZ_ASSERT(!rv.Failed());
+
+ mSelectionBarEnabled = aEnabled;
+}
+
+/* static */ nsAutoString
+AccessibleCaret::AppearanceString(Appearance aAppearance)
+{
+ nsAutoString string;
+ switch (aAppearance) {
+ case Appearance::None:
+ case Appearance::NormalNotShown:
+ string = NS_LITERAL_STRING("none");
+ break;
+ case Appearance::Normal:
+ string = NS_LITERAL_STRING("normal");
+ break;
+ case Appearance::Right:
+ string = NS_LITERAL_STRING("right");
+ break;
+ case Appearance::Left:
+ string = NS_LITERAL_STRING("left");
+ 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(NS_LITERAL_STRING("touchstart"),
+ mDummyTouchListener, false);
+ }
+}
+
+void
+AccessibleCaret::InjectCaretElement(nsIDocument* aDocument)
+{
+ ErrorResult rv;
+ nsCOMPtr<Element> element = CreateCaretElement(aDocument);
+ mCaretElementHolder = aDocument->InsertAnonymousContent(*element, rv);
+
+ MOZ_ASSERT(!rv.Failed(), "Insert anonymous content should not fail!");
+ MOZ_ASSERT(mCaretElementHolder.get(), "We must have anonymous content!");
+
+ // InsertAnonymousContent will clone the element to make an AnonymousContent.
+ // Since event listeners are not being cloned when cloning a node, we need to
+ // add the listener here.
+ EnsureApzAware();
+}
+
+already_AddRefed<Element>
+AccessibleCaret::CreateCaretElement(nsIDocument* aDocument) const
+{
+ // Content structure of AccessibleCaret
+ // <div class="moz-accessiblecaret"> <- CaretElement()
+ // <div id="text-overlay" <- TextOverlayElement()
+ // <div id="image"> <- CaretImageElement()
+ // <div id="bar"> <- SelectionBarElement()
+
+ ErrorResult rv;
+ nsCOMPtr<Element> parent = aDocument->CreateHTMLElement(nsGkAtoms::div);
+ parent->ClassList()->Add(NS_LITERAL_STRING("moz-accessiblecaret"), rv);
+ parent->ClassList()->Add(NS_LITERAL_STRING("none"), rv);
+ parent->ClassList()->Add(NS_LITERAL_STRING("no-bar"), rv);
+
+ auto CreateAndAppendChildElement = [aDocument, &parent](
+ const nsLiteralString& aElementId)
+ {
+ nsCOMPtr<Element> child = aDocument->CreateHTMLElement(nsGkAtoms::div);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, true);
+ parent->AppendChildTo(child, false);
+ };
+
+ CreateAndAppendChildElement(sTextOverlayElementId);
+ CreateAndAppendChildElement(sCaretImageElementId);
+ CreateAndAppendChildElement(sSelectionBarElementId);
+
+ return parent.forget();
+}
+
+void
+AccessibleCaret::RemoveCaretElement(nsIDocument* aDocument)
+{
+ CaretElement()->RemoveEventListener(NS_LITERAL_STRING("touchstart"),
+ mDummyTouchListener, false);
+
+ ErrorResult rv;
+ aDocument->RemoveAnonymousContent(*mCaretElementHolder, rv);
+ // It's OK rv is failed since nsCanvasFrame might not exists now.
+ rv.SuppressException();
+}
+
+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.
+ mImaginaryCaretRect = nsRect();
+ mZoomLevel = 0.0f;
+ return PositionChangedResult::Invisible;
+ }
+
+ nsRect imaginaryCaretRect = imaginaryCaretRectInFrame;
+ nsLayoutUtils::TransformRect(aFrame, RootFrame(), imaginaryCaretRect);
+ float zoomLevel = GetZoomLevel();
+
+ if (imaginaryCaretRect.IsEqualEdges(mImaginaryCaretRect) &&
+ FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel)) {
+ return PositionChangedResult::NotChanged;
+ }
+
+ mImaginaryCaretRect = imaginaryCaretRect;
+ mZoomLevel = zoomLevel;
+
+ // SetCaretElementStyle() requires the input rect relative to container frame.
+ nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
+ nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
+ imaginaryCaretRectInContainerFrame);
+ SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
+
+ return PositionChangedResult::Changed;
+}
+
+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;
+ styleStr.AppendPrintf("left: %dpx; top: %dpx; "
+ "width: %.2fpx; height: %.2fpx; margin-left: %.2fpx",
+ nsPresContext::AppUnitsToIntCSSPixels(position.x),
+ nsPresContext::AppUnitsToIntCSSPixels(position.y),
+ sWidth / aZoomLevel,
+ sHeight / aZoomLevel,
+ sMarginLeft / aZoomLevel);
+
+ 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);
+ SetSelectionBarElementStyle(aRect, aZoomLevel);
+}
+
+void
+AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
+ float aZoomLevel)
+{
+ nsAutoString styleStr;
+ styleStr.AppendPrintf("height: %dpx;",
+ nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
+ 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.AppendPrintf("margin-top: %dpx;",
+ nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
+ CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
+ true);
+ AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
+}
+
+void
+AccessibleCaret::SetSelectionBarElementStyle(const nsRect& aRect,
+ float aZoomLevel)
+{
+ nsAutoString styleStr;
+ styleStr.AppendPrintf("height: %dpx; width: %.2fpx;",
+ nsPresContext::AppUnitsToIntCSSPixels(aRect.height),
+ sBarWidth / aZoomLevel);
+ SelectionBarElement()->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 B2G or 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 000000000..42e5bb386
--- /dev/null
+++ b/layout/base/AccessibleCaret.h
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/Element.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsISupportsBase.h"
+#include "nsISupportsImpl.h"
+#include "nsLiteralString.h"
+#include "nsRect.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+class nsIDocument;
+class nsIFrame;
+class nsIPresShell;
+struct nsPoint;
+
+namespace mozilla {
+
+// -----------------------------------------------------------------------------
+// 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(nsIPresShell* 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);
+ }
+
+ // Set true to enable the "Text Selection Bar" described in "Text Selection
+ // Visual Spec" in bug 921965.
+ virtual void SetSelectionBarEnabled(bool aEnabled);
+
+ // This enumeration representing the result returned by SetPosition().
+ enum class PositionChangedResult : uint8_t {
+ // Position is not changed.
+ NotChanged,
+
+ // Position or zoom level is changed.
+ Changed,
+
+ // 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. Container of image and bar elements.
+ dom::Element* CaretElement() const
+ {
+ return mCaretElementHolder->GetContentNode();
+ }
+
+ // 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();
+
+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);
+ void SetSelectionBarElementStyle(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
+ {
+ return mCaretElementHolder->GetElementById(sTextOverlayElementId);
+ }
+
+ // Element which contains the caret image for 'Contains' test.
+ dom::Element* CaretImageElement() const
+ {
+ return mCaretElementHolder->GetElementById(sCaretImageElementId);
+ }
+
+ // Element which represents the text selection bar.
+ dom::Element* SelectionBarElement() const
+ {
+ return mCaretElementHolder->GetElementById(sSelectionBarElementId);
+ }
+
+ nsIFrame* RootFrame() const
+ {
+ return mPresShell->GetRootFrame();
+ }
+
+ nsIFrame* CustomContentContainerFrame() const;
+
+ // Transform Appearance to CSS id used in ua.css.
+ static nsAutoString AppearanceString(Appearance aAppearance);
+
+ already_AddRefed<dom::Element> CreateCaretElement(nsIDocument* aDocument) const;
+
+ // Inject caret element into custom content container.
+ void InjectCaretElement(nsIDocument* aDocument);
+
+ // Remove caret element from custom content container.
+ void RemoveCaretElement(nsIDocument* aDocument);
+
+ // 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(nsIDOMEvent* aEvent) override
+ {
+ return NS_OK;
+ }
+
+ private:
+ virtual ~DummyTouchListener() {};
+ };
+
+ // Member variables
+ Appearance mAppearance = Appearance::None;
+
+ bool mSelectionBarEnabled = false;
+
+ // 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.
+ nsIPresShell* MOZ_NON_OWNING_REF const mPresShell = nullptr;
+
+ RefPtr<dom::AnonymousContent> mCaretElementHolder;
+
+ // mImaginaryCaretRect is relative to root frame.
+ nsRect mImaginaryCaretRect;
+
+ // Cache current zoom level to determine whether position is changed.
+ float mZoomLevel = 0.0f;
+
+ // A no-op touch-start listener which prevents APZ from panning when dragging
+ // the caret.
+ RefPtr<DummyTouchListener> mDummyTouchListener{new DummyTouchListener()};
+
+ // Static class variables
+ static float sWidth;
+ static float sHeight;
+ static float sMarginLeft;
+ static float sBarWidth;
+ static const nsLiteralString sTextOverlayElementId;
+ static const nsLiteralString sCaretImageElementId;
+ static const nsLiteralString sSelectionBarElementId;
+
+}; // 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 000000000..b897e4d77
--- /dev/null
+++ b/layout/base/AccessibleCaretEventHub.cpp
@@ -0,0 +1,829 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "Layers.h"
+#include "gfxPrefs.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Preferences.h"
+#include "nsCanvasFrame.h"
+#include "nsDocShell.h"
+#include "nsFocusManager.h"
+#include "nsFrameSelection.h"
+#include "nsITimer.h"
+#include "nsPresContext.h"
+
+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,
+ nsISelectionListener, nsISupportsWeakReference);
+
+// -----------------------------------------------------------------------------
+// NoActionState
+//
+class AccessibleCaretEventHub::NoActionState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "NoActionState"; }
+
+ virtual 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(aContext->PressCaretState());
+ rv = nsEventStatus_eConsumeNoDefault;
+ } else {
+ aContext->SetState(aContext->PressNoCaretState());
+ }
+
+ aContext->mPressPoint = aPoint;
+ aContext->mActiveTouchId = aTouchId;
+
+ return rv;
+ }
+
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(aContext->ScrollState());
+ }
+
+ virtual void OnScrollPositionChanged(
+ AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnScrollPositionChanged();
+ }
+
+ virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
+ nsIDOMDocument* aDoc, nsISelection* aSel,
+ int16_t aReason) override
+ {
+ aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
+ }
+
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override
+ {
+ aContext->mManager->OnBlur();
+ }
+
+ virtual void OnReflow(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnReflow();
+ }
+
+ virtual void Enter(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mPressPoint = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ aContext->mActiveTouchId = kInvalidTouchId;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// PressCaretState: Always consume the event since we've pressed on the caret.
+//
+class AccessibleCaretEventHub::PressCaretState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "PressCaretState"; }
+
+ virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override
+ {
+ if (aContext->MoveDistanceIsLarge(aPoint)) {
+ if (NS_SUCCEEDED(aContext->mManager->DragCaret(aPoint))) {
+ aContext->SetState(aContext->DragCaretState());
+ }
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->ReleaseCaret();
+ aContext->mManager->TapCaret(aContext->mPressPoint);
+ aContext->SetState(aContext->NoActionState());
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override
+ {
+ return nsEventStatus_eConsumeNoDefault;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// DragCaretState: Always consume the event since we've pressed on the caret.
+//
+class AccessibleCaretEventHub::DragCaretState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "DragCaretState"; }
+
+ virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override
+ {
+ aContext->mManager->DragCaret(aPoint);
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->ReleaseCaret();
+ aContext->SetState(aContext->NoActionState());
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// PressNoCaretState
+//
+class AccessibleCaretEventHub::PressNoCaretState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "PressNoCaretState"; }
+
+ virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override
+ {
+ if (aContext->MoveDistanceIsLarge(aPoint)) {
+ aContext->SetState(aContext->NoActionState());
+ }
+
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->SetState(aContext->NoActionState());
+
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override
+ {
+ aContext->SetState(aContext->LongTapState());
+
+ return aContext->GetState()->OnLongTap(aContext, aPoint);
+ }
+
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(aContext->ScrollState());
+ }
+
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override
+ {
+ aContext->mManager->OnBlur();
+ if (aIsLeavingDocument) {
+ aContext->SetState(aContext->NoActionState());
+ }
+ }
+
+ virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
+ nsIDOMDocument* aDoc, nsISelection* aSel,
+ int16_t aReason) override
+ {
+ aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
+ }
+
+ virtual void OnReflow(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnReflow();
+ }
+
+ virtual void Enter(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->LaunchLongTapInjector();
+ }
+
+ virtual void Leave(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->CancelLongTapInjector();
+ }
+};
+
+// -----------------------------------------------------------------------------
+// ScrollState
+//
+class AccessibleCaretEventHub::ScrollState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "ScrollState"; }
+
+ virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->SetState(aContext->PostScrollState());
+ }
+
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override
+ {
+ aContext->mManager->OnBlur();
+ if (aIsLeavingDocument) {
+ aContext->SetState(aContext->NoActionState());
+ }
+ }
+};
+
+// -----------------------------------------------------------------------------
+// PostScrollState: In this state, we are waiting for another APZ start or press
+// event.
+//
+class AccessibleCaretEventHub::PostScrollState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "PostScrollState"; }
+
+ virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint, int32_t aTouchId,
+ EventClassID aEventClass) override
+ {
+ aContext->mManager->OnScrollEnd();
+ aContext->SetState(aContext->NoActionState());
+
+ return aContext->GetState()->OnPress(aContext, aPoint, aTouchId,
+ aEventClass);
+ }
+
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->SetState(aContext->ScrollState());
+ }
+
+ virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnScrollEnd();
+ aContext->SetState(aContext->NoActionState());
+ }
+
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override
+ {
+ aContext->mManager->OnBlur();
+ if (aIsLeavingDocument) {
+ aContext->SetState(aContext->NoActionState());
+ }
+ }
+
+ virtual void Enter(AccessibleCaretEventHub* aContext) override
+ {
+ // Launch the injector to leave PostScrollState.
+ aContext->LaunchScrollEndInjector();
+ }
+
+ virtual void Leave(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->CancelScrollEndInjector();
+ }
+};
+
+// -----------------------------------------------------------------------------
+// LongTapState
+//
+class AccessibleCaretEventHub::LongTapState
+ : public AccessibleCaretEventHub::State
+{
+public:
+ virtual const char* Name() const override { return "LongTapState"; }
+
+ virtual 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;
+ }
+
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->SetState(aContext->NoActionState());
+
+ // Do not consume the release since the press is not consumed in
+ // PressNoCaretState either.
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
+ {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(aContext->ScrollState());
+ }
+
+ virtual 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(PostScrollState)
+MOZ_IMPL_STATE_CLASS_GETTER(LongTapState)
+
+bool AccessibleCaretEventHub::sUseLongTapInjector = false;
+
+AccessibleCaretEventHub::AccessibleCaretEventHub(nsIPresShell* aPresShell)
+ : mPresShell(aPresShell)
+{
+ static bool prefsAdded = false;
+ if (!prefsAdded) {
+ Preferences::AddBoolVarCache(
+ &sUseLongTapInjector, "layout.accessiblecaret.use_long_tap_injector");
+ prefsAdded = true;
+ }
+}
+
+AccessibleCaretEventHub::~AccessibleCaretEventHub()
+{
+}
+
+void
+AccessibleCaretEventHub::Init()
+{
+ if (mInitialized && mManager) {
+ mManager->OnFrameReconstruction();
+ }
+
+ if (mInitialized || !mPresShell || !mPresShell->GetCanvasFrame() ||
+ !mPresShell->GetCanvasFrame()->GetCustomContentContainer()) {
+ 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()");
+
+ nsIDocShell* docShell = presContext->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ docShell->AddWeakReflowObserver(this);
+ docShell->AddWeakScrollObserver(this);
+
+ mDocShell = static_cast<nsDocShell*>(docShell);
+
+ if (sUseLongTapInjector) {
+ mLongTapInjectorTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ mScrollEndInjectorTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ mManager = MakeUnique<AccessibleCaretManager>(mPresShell);
+
+ mInitialized = true;
+}
+
+void
+AccessibleCaretEventHub::Terminate()
+{
+ if (!mInitialized) {
+ return;
+ }
+
+ RefPtr<nsDocShell> docShell(mDocShell.get());
+ if (docShell) {
+ docShell->RemoveWeakReflowObserver(this);
+ docShell->RemoveWeakScrollObserver(this);
+ }
+
+ if (mLongTapInjectorTimer) {
+ mLongTapInjectorTimer->Cancel();
+ }
+
+ if (mScrollEndInjectorTimer) {
+ mScrollEndInjectorTimer->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:
+ break;
+ }
+
+ return status;
+}
+
+nsEventStatus
+AccessibleCaretEventHub::HandleMouseEvent(WidgetMouseEvent* aEvent)
+{
+ nsEventStatus rv = nsEventStatus_eIgnore;
+
+ if (aEvent->button != WidgetMouseEvent::eLeftButton) {
+ 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->inputSource);
+ }
+
+ 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());
+ rv = mState->OnMove(this, point);
+ 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(nsIDOMMouseEvent::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());
+ rv = mState->OnMove(this, point);
+ 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(nsIDOMMouseEvent::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) >
+ nsPresContext::AppUnitsPerCSSPixel() * kMoveStartToleranceInPixel;
+}
+
+void
+AccessibleCaretEventHub::LaunchLongTapInjector()
+{
+ if (!mLongTapInjectorTimer) {
+ return;
+ }
+
+ int32_t longTapDelay = gfxPrefs::UiClickHoldContextMenusDelay();
+ mLongTapInjectorTimer->InitWithFuncCallback(FireLongTap, this, longTapDelay,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+AccessibleCaretEventHub::CancelLongTapInjector()
+{
+ if (!mLongTapInjectorTimer) {
+ return;
+ }
+
+ mLongTapInjectorTimer->Cancel();
+}
+
+/* static */ void
+AccessibleCaretEventHub::FireLongTap(nsITimer* aTimer,
+ void* aAccessibleCaretEventHub)
+{
+ auto* self = static_cast<AccessibleCaretEventHub*>(aAccessibleCaretEventHub);
+ self->mState->OnLongTap(self, self->mPressPoint);
+}
+
+NS_IMETHODIMP
+AccessibleCaretEventHub::Reflow(DOMHighResTimeStamp aStart,
+ DOMHighResTimeStamp aEnd)
+{
+ if (!mInitialized) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ if (mIsInReflowCallback) {
+ return NS_OK;
+ }
+
+ AutoRestore<bool> autoRestoreIsInReflowCallback(mIsInReflowCallback);
+ mIsInReflowCallback = true;
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnReflow(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AccessibleCaretEventHub::ReflowInterruptible(DOMHighResTimeStamp aStart,
+ DOMHighResTimeStamp aEnd)
+{
+ // Defer the error checking to Reflow().
+ return Reflow(aStart, aEnd);
+}
+
+void
+AccessibleCaretEventHub::AsyncPanZoomStarted()
+{
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollStart(this);
+}
+
+void
+AccessibleCaretEventHub::AsyncPanZoomStopped()
+{
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollEnd(this);
+}
+
+void
+AccessibleCaretEventHub::ScrollPositionChanged()
+{
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollPositionChanged(this);
+}
+
+void
+AccessibleCaretEventHub::LaunchScrollEndInjector()
+{
+ if (!mScrollEndInjectorTimer) {
+ return;
+ }
+
+ mScrollEndInjectorTimer->InitWithFuncCallback(
+ FireScrollEnd, this, kScrollEndTimerDelay, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+AccessibleCaretEventHub::CancelScrollEndInjector()
+{
+ if (!mScrollEndInjectorTimer) {
+ return;
+ }
+
+ mScrollEndInjectorTimer->Cancel();
+}
+
+/* static */ void
+AccessibleCaretEventHub::FireScrollEnd(nsITimer* aTimer,
+ void* aAccessibleCaretEventHub)
+{
+ auto* self = static_cast<AccessibleCaretEventHub*>(aAccessibleCaretEventHub);
+ self->mState->OnScrollEnd(self);
+}
+
+nsresult
+AccessibleCaretEventHub::NotifySelectionChanged(nsIDOMDocument* aDoc,
+ nsISelection* aSel,
+ int16_t aReason)
+{
+ if (!mInitialized) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s, reason: %d", __FUNCTION__, mState->Name(), aReason);
+ mState->OnSelectionChanged(this, aDoc, aSel, aReason);
+ return NS_OK;
+}
+
+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,
+ 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,
+ rootFrame);
+}
+
+} // namespace mozilla
diff --git a/layout/base/AccessibleCaretEventHub.h b/layout/base/AccessibleCaretEventHub.h
new file mode 100644
index 000000000..6681f8669
--- /dev/null
+++ b/layout/base/AccessibleCaretEventHub.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 AccessibleCaretEventHub_h
+#define AccessibleCaretEventHub_h
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsIReflowObserver.h"
+#include "nsIScrollObserver.h"
+#include "nsISelectionListener.h"
+#include "nsPoint.h"
+#include "mozilla/RefPtr.h"
+#include "nsWeakReference.h"
+
+class nsDocShell;
+class nsIPresShell;
+class nsITimer;
+
+namespace mozilla {
+class AccessibleCaretManager;
+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.
+//
+// State transition diagram:
+// http://hg.mozilla.org/mozilla-central/raw-file/default/layout/base/doc/AccessibleCaretEventHubStates.png
+// Source code of the diagram:
+// http://hg.mozilla.org/mozilla-central/file/default/layout/base/doc/AccessibleCaretEventHubStates.dot
+//
+// Please see the wiki page for more information.
+// https://wiki.mozilla.org/AccessibleCaret
+//
+class AccessibleCaretEventHub : public nsIReflowObserver,
+ public nsIScrollObserver,
+ public nsISelectionListener,
+ public nsSupportsWeakReference
+{
+public:
+ explicit AccessibleCaretEventHub(nsIPresShell* aPresShell);
+ void Init();
+ void Terminate();
+
+ nsEventStatus HandleEvent(WidgetEvent* aEvent);
+
+ // Call this function to notify the blur event happened.
+ void NotifyBlur(bool aIsLeavingDocument);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREFLOWOBSERVER
+ NS_DECL_NSISELECTIONLISTENER
+
+ // Override nsIScrollObserver methods.
+ virtual void ScrollPositionChanged() override;
+ virtual void AsyncPanZoomStarted() override;
+ virtual void AsyncPanZoomStopped() override;
+
+ // Base state
+ class State;
+ State* GetState() const;
+
+protected:
+ virtual ~AccessibleCaretEventHub();
+
+#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(PostScrollState)
+ MOZ_DECL_STATE_CLASS_GETTER(LongTapState)
+
+ void SetState(State* aState);
+
+ nsEventStatus HandleMouseEvent(WidgetMouseEvent* aEvent);
+ nsEventStatus HandleTouchEvent(WidgetTouchEvent* aEvent);
+ 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();
+ static void FireLongTap(nsITimer* aTimer, void* aAccessibleCaretEventHub);
+
+ void LaunchScrollEndInjector();
+ void CancelScrollEndInjector();
+ static void FireScrollEnd(nsITimer* aTimer, void* aAccessibleCaretEventHub);
+
+ // Member variables
+ State* mState = NoActionState();
+
+ // Will be set to nullptr in Terminate().
+ nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
+
+ UniquePtr<AccessibleCaretManager> mManager;
+
+ WeakPtr<nsDocShell> mDocShell;
+
+ // Use this timer for injecting a long tap event when APZ is disabled. If APZ
+ // is enabled, it will send long tap event to us.
+ nsCOMPtr<nsITimer> mLongTapInjectorTimer;
+
+ // Use this timer for injecting a simulated scroll end.
+ nsCOMPtr<nsITimer> mScrollEndInjectorTimer;
+
+ // 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;
+
+ // Simulate long tap if the platform does not support eMouseLongTap events.
+ static bool sUseLongTapInjector;
+
+ static const int32_t kScrollEndTimerDelay = 300;
+ 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 ""; }
+
+ virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint, int32_t aTouchId,
+ EventClassID aEventClass)
+ {
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint)
+ {
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext)
+ {
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint)
+ {
+ return nsEventStatus_eIgnore;
+ }
+
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) {}
+ virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) {}
+ virtual void OnScrollPositionChanged(AccessibleCaretEventHub* aContext) {}
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) {}
+ virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
+ nsIDOMDocument* aDoc, nsISelection* aSel,
+ int16_t aReason) {}
+ 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 // AccessibleCaretEventHub_h
diff --git a/layout/base/AccessibleCaretLogger.h b/layout/base/AccessibleCaretLogger.h
new file mode 100644
index 000000000..9a0235643
--- /dev/null
+++ b/layout/base/AccessibleCaretLogger.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 000000000..ac3f36bea
--- /dev/null
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -0,0 +1,1474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "AccessibleCaret.h"
+#include "AccessibleCaretEventHub.h"
+#include "AccessibleCaretLogger.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/Preferences.h"
+#include "nsCaret.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsFrame.h"
+#include "nsFrameSelection.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIHapticFeedback.h"
+#ifdef MOZ_WIDGET_ANDROID
+#include "nsWindow.h"
+#endif
+
+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);
+ }
+ return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
+/*static*/ bool
+AccessibleCaretManager::sSelectionBarEnabled = false;
+/*static*/ bool
+AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = false;
+/*static*/ bool
+AccessibleCaretManager::sCaretsAlwaysTilt = false;
+/*static*/ bool
+AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = true;
+/*static*/ bool
+AccessibleCaretManager::sCaretsScriptUpdates = false;
+/*static*/ bool
+AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
+/*static*/ bool
+AccessibleCaretManager::sHapticFeedback = false;
+/*static*/ bool
+AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
+/*static*/ bool
+AccessibleCaretManager::sHideCaretsForMouseInput = true;
+
+AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
+ : mPresShell(aPresShell)
+{
+ if (!mPresShell) {
+ return;
+ }
+
+ mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
+ mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
+
+ mCaretTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ static bool addedPrefs = false;
+ if (!addedPrefs) {
+ Preferences::AddBoolVarCache(&sSelectionBarEnabled,
+ "layout.accessiblecaret.bar.enabled");
+ Preferences::AddBoolVarCache(&sCaretShownWhenLongTappingOnEmptyContent,
+ "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content");
+ Preferences::AddBoolVarCache(&sCaretsAlwaysTilt,
+ "layout.accessiblecaret.always_tilt");
+ Preferences::AddBoolVarCache(&sCaretsAlwaysShowWhenScrolling,
+ "layout.accessiblecaret.always_show_when_scrolling", true);
+ Preferences::AddBoolVarCache(&sCaretsScriptUpdates,
+ "layout.accessiblecaret.allow_script_change_updates");
+ Preferences::AddBoolVarCache(&sCaretsAllowDraggingAcrossOtherCaret,
+ "layout.accessiblecaret.allow_dragging_across_other_caret", true);
+ Preferences::AddBoolVarCache(&sHapticFeedback,
+ "layout.accessiblecaret.hapticfeedback");
+ Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
+ "layout.accessiblecaret.extend_selection_for_phone_number");
+ Preferences::AddBoolVarCache(&sHideCaretsForMouseInput,
+ "layout.accessiblecaret.hide_carets_for_mouse_input");
+ addedPrefs = true;
+ }
+}
+
+AccessibleCaretManager::~AccessibleCaretManager()
+{
+}
+
+void
+AccessibleCaretManager::Terminate()
+{
+ CancelCaretTimeoutTimer();
+ mCaretTimeoutTimer = nullptr;
+ mFirstCaret = nullptr;
+ mSecondCaret = nullptr;
+ mActiveCaret = nullptr;
+ mPresShell = nullptr;
+}
+
+nsresult
+AccessibleCaretManager::OnSelectionChanged(nsIDOMDocument* aDoc,
+ nsISelection* 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.
+ if (aReason == nsISelectionListener::NO_REASON) {
+ // Update visible carets, if javascript changes are allowed.
+ if (sCaretsScriptUpdates &&
+ (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible())) {
+ UpdateCarets();
+ return NS_OK;
+ }
+ // Default for NO_REASON is to make hidden.
+ HideCarets();
+ return NS_OK;
+ }
+
+ // Move cursor by keyboard.
+ if (aReason & nsISelectionListener::KEYPRESS_REASON) {
+ HideCarets();
+ 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) {
+ HideCarets();
+ return NS_OK;
+ }
+
+ // Range will collapse after cutting or copying text.
+ if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
+ nsISelectionListener::COLLAPSETOEND_REASON)) {
+ HideCarets();
+ return NS_OK;
+ }
+
+ // For mouse input we don't want to show the carets.
+ if (sHideCaretsForMouseInput &&
+ mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
+ HideCarets();
+ 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 (sHideCaretsForMouseInput &&
+ mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD &&
+ (aReason & nsISelectionListener::SELECTALL_REASON)) {
+ HideCarets();
+ return NS_OK;
+ }
+
+ UpdateCarets();
+ return NS_OK;
+}
+
+void
+AccessibleCaretManager::HideCarets()
+{
+ if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+ AC_LOG("%s", __FUNCTION__);
+ mFirstCaret->SetAppearance(Appearance::None);
+ mSecondCaret->SetAppearance(Appearance::None);
+ DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
+ CancelCaretTimeoutTimer();
+ }
+}
+
+void
+AccessibleCaretManager::UpdateCarets(UpdateCaretsHint aHint)
+{
+ FlushLayout();
+ if (IsTerminated()) {
+ return;
+ }
+
+ mLastUpdateCaretMode = GetCaretMode();
+
+ switch (mLastUpdateCaretMode) {
+ case CaretMode::None:
+ HideCarets();
+ break;
+ case CaretMode::Cursor:
+ UpdateCaretsForCursorMode(aHint);
+ break;
+ case CaretMode::Selection:
+ UpdateCaretsForSelectionMode(aHint);
+ break;
+ }
+}
+
+bool
+AccessibleCaretManager::IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame,
+ int32_t* aOutOffset) const
+{
+ RefPtr<nsCaret> caret = mPresShell->GetCaret();
+ if (!caret || !caret->IsVisible()) {
+ return false;
+ }
+
+ int32_t offset = 0;
+ nsIFrame* frame = nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
+
+ if (!frame) {
+ return false;
+ }
+
+ if (!GetEditingHostForFrame(frame)) {
+ return false;
+ }
+
+ if (aOutFrame) {
+ *aOutFrame = frame;
+ }
+
+ if (aOutOffset) {
+ *aOutOffset = offset;
+ }
+
+ return true;
+}
+
+bool
+AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const
+{
+ return nsContentUtils::HasNonEmptyTextContent(
+ aNode, nsContentUtils::eRecurseIntoChildren);
+}
+
+void
+AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHint aHint)
+{
+ AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
+
+ int32_t offset = 0;
+ nsIFrame* frame = nullptr;
+ if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
+ HideCarets();
+ return;
+ }
+
+ bool oldSecondCaretVisible = mSecondCaret->IsLogicallyVisible();
+ PositionChangedResult result = mFirstCaret->SetPosition(frame, offset);
+
+ switch (result) {
+ case PositionChangedResult::NotChanged:
+ // Do nothing
+ break;
+
+ case PositionChangedResult::Changed:
+ switch (aHint) {
+ case UpdateCaretsHint::Default:
+ if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
+ mFirstCaret->SetAppearance(Appearance::Normal);
+ } else if (sCaretShownWhenLongTappingOnEmptyContent) {
+ if (mFirstCaret->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.
+ mFirstCaret->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 {
+ mFirstCaret->SetAppearance(Appearance::NormalNotShown);
+ }
+ break;
+
+ case UpdateCaretsHint::RespectOldAppearance:
+ // Do nothing to preserve the appearance of the caret set by the
+ // caller.
+ break;
+ }
+ break;
+
+ case PositionChangedResult::Invisible:
+ mFirstCaret->SetAppearance(Appearance::NormalNotShown);
+ break;
+ }
+
+ mFirstCaret->SetSelectionBarEnabled(false);
+ mSecondCaret->SetAppearance(Appearance::None);
+
+ LaunchCaretTimeoutTimer();
+
+ if ((result != PositionChangedResult::NotChanged || oldSecondCaretVisible) &&
+ !mActiveCaret) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
+ }
+}
+
+void
+AccessibleCaretManager::UpdateCaretsForSelectionMode(UpdateCaretsHint aHint)
+{
+ 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?
+ HideCarets();
+ return;
+ }
+
+ auto updateSingleCaret = [aHint](AccessibleCaret* aCaret, nsIFrame* aFrame,
+ int32_t aOffset) -> PositionChangedResult
+ {
+ PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
+ aCaret->SetSelectionBarEnabled(sSelectionBarEnabled);
+
+ switch (result) {
+ case PositionChangedResult::NotChanged:
+ // Do nothing
+ break;
+
+ case PositionChangedResult::Changed:
+ switch (aHint) {
+ case UpdateCaretsHint::Default:
+ aCaret->SetAppearance(Appearance::Normal);
+ break;
+
+ case UpdateCaretsHint::RespectOldAppearance:
+ // Do nothing to preserve the appearance of the caret set by the
+ // caller.
+ break;
+ }
+ break;
+
+ case PositionChangedResult::Invisible:
+ aCaret->SetAppearance(Appearance::NormalNotShown);
+ break;
+ }
+ return result;
+ };
+
+ PositionChangedResult firstCaretResult =
+ updateSingleCaret(mFirstCaret.get(), startFrame, startOffset);
+ PositionChangedResult secondCaretResult =
+ updateSingleCaret(mSecondCaret.get(), endFrame, endOffset);
+
+ if (firstCaretResult == PositionChangedResult::Changed ||
+ secondCaretResult == PositionChangedResult::Changed) {
+ // Flush layout to make the carets intersection correct.
+ FlushLayout();
+ if (IsTerminated()) {
+ return;
+ }
+ }
+
+ if (aHint == UpdateCaretsHint::Default) {
+ // Only check for tilt carets with default update hint. Otherwise we might
+ // override the appearance set by the caller.
+ if (sCaretsAlwaysTilt) {
+ UpdateCaretsForAlwaysTilt(startFrame, endFrame);
+ } else {
+ UpdateCaretsForOverlappingTilt();
+ }
+ }
+
+ if (!mActiveCaret) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
+ }
+}
+
+void
+AccessibleCaretManager::UpdateCaretsForOverlappingTilt()
+{
+ if (mFirstCaret->IsVisuallyVisible() && mSecondCaret->IsVisuallyVisible()) {
+ if (mFirstCaret->Intersects(*mSecondCaret)) {
+ if (mFirstCaret->LogicalPosition().x <=
+ mSecondCaret->LogicalPosition().x) {
+ mFirstCaret->SetAppearance(Appearance::Left);
+ mSecondCaret->SetAppearance(Appearance::Right);
+ } else {
+ mFirstCaret->SetAppearance(Appearance::Right);
+ mSecondCaret->SetAppearance(Appearance::Left);
+ }
+ } else {
+ mFirstCaret->SetAppearance(Appearance::Normal);
+ mSecondCaret->SetAppearance(Appearance::Normal);
+ }
+ }
+}
+
+void
+AccessibleCaretManager::UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame)
+{
+ if (mFirstCaret->IsVisuallyVisible()) {
+ auto startFrameWritingMode = aStartFrame->GetWritingMode();
+ mFirstCaret->SetAppearance(startFrameWritingMode.IsBidiLTR() ?
+ Appearance::Left : Appearance::Right);
+ }
+ if (mSecondCaret->IsVisuallyVisible()) {
+ auto endFrameWritingMode = aEndFrame->GetWritingMode();
+ mSecondCaret->SetAppearance(endFrameWritingMode.IsBidiLTR() ?
+ Appearance::Right : Appearance::Left);
+ }
+}
+
+void
+AccessibleCaretManager::ProvideHapticFeedback()
+{
+ if (sHapticFeedback) {
+ nsCOMPtr<nsIHapticFeedback> haptic =
+ do_GetService("@mozilla.org/widget/hapticfeedback;1");
+ haptic->PerformSimpleAction(haptic->LongPress);
+ }
+}
+
+nsresult
+AccessibleCaretManager::PressCaret(const nsPoint& aPoint,
+ EventClassID aEventClass)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ MOZ_ASSERT(aEventClass == eMouseEventClass || aEventClass == eTouchEventClass,
+ "Unexpected event class!");
+
+ using TouchArea = AccessibleCaret::TouchArea;
+ TouchArea touchArea =
+ aEventClass == eMouseEventClass ? TouchArea::CaretImage : TouchArea::Full;
+
+ if (mFirstCaret->Contains(aPoint, touchArea)) {
+ mActiveCaret = mFirstCaret.get();
+ SetSelectionDirection(eDirPrevious);
+ } else if (mSecondCaret->Contains(aPoint, touchArea)) {
+ mActiveCaret = mSecondCaret.get();
+ SetSelectionDirection(eDirNext);
+ }
+
+ if (mActiveCaret) {
+ mOffsetYToCaretLogicalPosition =
+ mActiveCaret->LogicalPosition().y - aPoint.y;
+ SetSelectionDragState(true);
+ DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret);
+ CancelCaretTimeoutTimer();
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult
+AccessibleCaretManager::DragCaret(const nsPoint& aPoint)
+{
+ MOZ_ASSERT(mActiveCaret);
+ MOZ_ASSERT(GetCaretMode() != CaretMode::None);
+
+ nsPoint point(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition);
+ DragCaretInternal(point);
+ UpdateCarets();
+ return NS_OK;
+}
+
+nsresult
+AccessibleCaretManager::ReleaseCaret()
+{
+ MOZ_ASSERT(mActiveCaret);
+
+ mActiveCaret = nullptr;
+ SetSelectionDragState(false);
+ DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
+ LaunchCaretTimeoutTimer();
+ 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);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult
+AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint)
+{
+ auto UpdateCaretsWithHapticFeedback = [this] {
+ UpdateCarets();
+ ProvideHapticFeedback();
+ };
+
+ // 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__);
+ UpdateCaretsWithHapticFeedback();
+ 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.
+ nsWeakFrame ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, aPoint,
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
+ if (!ptFrame.IsAlive()) {
+ 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(rootFrame, ptFrame, ptInFrame);
+
+ // Firstly check long press on an empty editable content.
+ Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
+ if (focusableFrame && newFocusEditingHost &&
+ !HasNonEmptyTextContent(newFocusEditingHost)) {
+ ChangeFocusToOrClearOldFocus(focusableFrame);
+
+ if (sCaretShownWhenLongTappingOnEmptyContent) {
+ mFirstCaret->SetAppearance(Appearance::Normal);
+ }
+ // We need to update carets to get correct information before dispatching
+ // CaretStateChangedEvent.
+ UpdateCaretsWithHapticFeedback();
+ DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
+ return NS_OK;
+ }
+
+ bool selectable = false;
+ ptFrame->IsSelectable(&selectable, 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;
+ }
+
+ // Then try select a word under point.
+ nsresult rv = SelectWord(ptFrame, ptInFrame);
+ UpdateCaretsWithHapticFeedback();
+
+ return rv;
+}
+
+void
+AccessibleCaretManager::OnScrollStart()
+{
+ AC_LOG("%s", __FUNCTION__);
+
+ if (!sCaretsAlwaysShowWhenScrolling) {
+ // Backup the appearance so that we can restore them after the scrolling
+ // ends.
+ mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance();
+ mSecondCaretAppearanceOnScrollStart = mSecondCaret->GetAppearance();
+ HideCarets();
+ return;
+ }
+
+ if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+ // Dispatch the event only if one of the carets is logically visible like in
+ // HideCarets().
+ DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
+ }
+}
+
+void
+AccessibleCaretManager::OnScrollEnd()
+{
+ if (mLastUpdateCaretMode != GetCaretMode()) {
+ return;
+ }
+
+ if (!sCaretsAlwaysShowWhenScrolling) {
+ // Restore the appearance which is saved before the scrolling is started.
+ mFirstCaret->SetAppearance(mFirstCaretAppearanceOnScrollStart);
+ mSecondCaret->SetAppearance(mSecondCaretAppearanceOnScrollStart);
+ }
+
+ if (GetCaretMode() == CaretMode::Cursor) {
+ if (!mFirstCaret->IsLogicallyVisible()) {
+ // If the caret is hidden (Appearance::None) due to timeout or blur, no
+ // need to update it.
+ return;
+ }
+ }
+
+ // For mouse input we don't want to show the carets.
+ if (sHideCaretsForMouseInput &&
+ mLastInputSource == nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {
+ AC_LOG("%s: HideCarets()", __FUNCTION__);
+ HideCarets();
+ return;
+ }
+
+ AC_LOG("%s: UpdateCarets()", __FUNCTION__);
+ UpdateCarets();
+}
+
+void
+AccessibleCaretManager::OnScrollPositionChanged()
+{
+ if (mLastUpdateCaretMode != GetCaretMode()) {
+ return;
+ }
+
+ if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+ AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+ UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+ }
+}
+
+void
+AccessibleCaretManager::OnReflow()
+{
+ if (mLastUpdateCaretMode != GetCaretMode()) {
+ return;
+ }
+
+ if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+ AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+ UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+ }
+}
+
+void
+AccessibleCaretManager::OnBlur()
+{
+ AC_LOG("%s: HideCarets()", __FUNCTION__);
+ HideCarets();
+}
+
+void
+AccessibleCaretManager::OnKeyboardEvent()
+{
+ if (GetCaretMode() == CaretMode::Cursor) {
+ AC_LOG("%s: HideCarets()", __FUNCTION__);
+ HideCarets();
+ }
+}
+
+void
+AccessibleCaretManager::OnFrameReconstruction()
+{
+ mFirstCaret->EnsureApzAware();
+ mSecondCaret->EnsureApzAware();
+}
+
+void
+AccessibleCaretManager::SetLastInputSource(uint16_t aInputSource)
+{
+ mLastInputSource = aInputSource;
+}
+
+Selection*
+AccessibleCaretManager::GetSelection() const
+{
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (!fs) {
+ return nullptr;
+ }
+ return fs->GetSelection(SelectionType::eNormal);
+}
+
+already_AddRefed<nsFrameSelection>
+AccessibleCaretManager::GetFrameSelection() const
+{
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ MOZ_ASSERT(fm);
+
+ nsIContent* focusedContent = fm->GetFocusedContent();
+ if (focusedContent) {
+ nsIFrame* focusFrame = focusedContent->GetPrimaryFrame();
+ if (!focusFrame) {
+ return nullptr;
+ }
+
+ // Prevent us from touching the nsFrameSelection associated with other
+ // PresShell.
+ RefPtr<nsFrameSelection> fs = focusFrame->GetFrameSelection();
+ if (!fs || fs->GetShell() != mPresShell) {
+ return nullptr;
+ }
+
+ return fs.forget();
+ } else {
+ // For non-editable content
+ return mPresShell->FrameSelection();
+ }
+}
+
+Element*
+AccessibleCaretManager::GetEditingHostForFrame(nsIFrame* aFrame) const
+{
+ if (!aFrame) {
+ return nullptr;
+ }
+
+ auto content = aFrame->GetContent();
+ if (!content) {
+ return nullptr;
+ }
+
+ return content->GetEditingHost();
+}
+
+
+AccessibleCaretManager::CaretMode
+AccessibleCaretManager::GetCaretMode() const
+{
+ Selection* selection = GetSelection();
+ if (!selection) {
+ return CaretMode::None;
+ }
+
+ uint32_t rangeCount = selection->RangeCount();
+ if (rangeCount <= 0) {
+ 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(nullptr, true)) {
+ break;
+ }
+ focusableFrame = focusableFrame->GetParent();
+ }
+ return focusableFrame;
+}
+
+void
+AccessibleCaretManager::ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const
+{
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ MOZ_ASSERT(fm);
+
+ if (aFrame) {
+ nsIContent* focusableContent = aFrame->GetContent();
+ MOZ_ASSERT(focusableContent, "Focusable frame must have content!");
+ nsCOMPtr<nsIDOMElement> focusableElement = do_QueryInterface(focusableContent);
+ fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYMOUSE);
+ } else {
+ nsPIDOMWindowOuter* win = mPresShell->GetDocument()->GetWindow();
+ if (win) {
+ fm->ClearFocus(win);
+ fm->SetFocusedWindow(win);
+ }
+ }
+}
+
+nsresult
+AccessibleCaretManager::SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const
+{
+ SetSelectionDragState(true);
+ nsFrame* frame = static_cast<nsFrame*>(aFrame);
+ nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), aPoint,
+ eSelectWord, eSelectWord, 0);
+
+ SetSelectionDragState(false);
+ ClearMaintainedSelection();
+
+ // Smart-select phone numbers if possible.
+ if (sExtendSelectionForPhoneNumber) {
+ SelectMoreIfPhoneNumber();
+ }
+
+ return rs;
+}
+
+void
+AccessibleCaretManager::SetSelectionDragState(bool aState) const
+{
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (fs) {
+ fs->SetDragState(aState);
+ }
+
+ // Pin Fennecs DynamicToolbarAnimator in place before/after dragging,
+ // to avoid co-incident screen scrolling.
+ #ifdef MOZ_WIDGET_ANDROID
+ nsIDocument* doc = mPresShell->GetDocument();
+ MOZ_ASSERT(doc);
+ nsIWidget* widget = nsContentUtils::WidgetForDocument(doc);
+ static_cast<nsWindow*>(widget)->SetSelectionDragState(aState);
+ #endif
+}
+
+void
+AccessibleCaretManager::SelectMoreIfPhoneNumber() const
+{
+ SetSelectionDirection(eDirNext);
+ ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
+
+ SetSelectionDirection(eDirPrevious);
+ ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
+
+ SetSelectionDirection(eDirNext);
+}
+
+void
+AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
+{
+ if (!mPresShell) {
+ return;
+ }
+
+ RefPtr<nsIDocument> doc = mPresShell->GetDocument();
+
+ // Extend the phone number selection until we find a boundary.
+ RefPtr<Selection> selection = GetSelection();
+
+ while (selection) {
+ const nsRange* anchorFocusRange = selection->GetAnchorFocusRange();
+ if (!anchorFocusRange) {
+ return;
+ }
+
+ // Backup the anchor focus range since both anchor node and focus node might
+ // be changed after calling Selection::Modify().
+ RefPtr<nsRange> oldAnchorFocusRange = anchorFocusRange->CloneRange();
+
+ // Save current Focus position, and extend the selection one char.
+ nsINode* focusNode = selection->GetFocusNode();
+ uint32_t focusOffset = selection->FocusOffset();
+ selection->Modify(NS_LITERAL_STRING("extend"),
+ aDirection,
+ NS_LITERAL_STRING("character"));
+ if (IsTerminated()) {
+ return;
+ }
+
+ // If the selection didn't change, (can't extend further), we're done.
+ if (selection->GetFocusNode() == focusNode &&
+ selection->FocusOffset() == focusOffset) {
+ return;
+ }
+
+ // If the changed selection isn't a valid phone number, we're done.
+ nsAutoString selectedText;
+ selection->Stringify(selectedText);
+ nsAutoString phoneRegex(NS_LITERAL_STRING("(^\\+)?[0-9\\s,\\-.()*#pw]{1,30}$"));
+
+ if (!nsContentUtils::IsPatternMatching(selectedText, phoneRegex, doc)) {
+ // Backout the undesired selection extend, restore the old anchor focus
+ // range before exit.
+ selection->SetAnchorFocusToRange(oldAnchorFocusRange);
+ return;
+ }
+ }
+}
+
+void
+AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const
+{
+ Selection* selection = GetSelection();
+ if (selection) {
+ selection->AdjustAnchorFocusForMultiRange(aDir);
+ }
+}
+
+void
+AccessibleCaretManager::ClearMaintainedSelection() const
+{
+ // Selection made by double-clicking for example will maintain the original
+ // word selection. We should clear it so that we can drag caret freely.
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (fs) {
+ fs->MaintainSelection(eSelectNoAmount);
+ }
+}
+
+void
+AccessibleCaretManager::FlushLayout() const
+{
+ if (mPresShell) {
+ mPresShell->FlushPendingNotifications(Flush_Layout);
+ }
+}
+
+nsIFrame*
+AccessibleCaretManager::GetFrameForFirstRangeStartOrLastRangeEnd(
+ nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode,
+ int32_t* aOutNodeOffset) const
+{
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
+ MOZ_ASSERT(aOutOffset, "aOutOffset shouldn't be nullptr!");
+
+ nsRange* range = nullptr;
+ RefPtr<nsINode> startNode;
+ RefPtr<nsINode> endNode;
+ int32_t nodeOffset = 0;
+ CaretAssociationHint hint;
+
+ RefPtr<Selection> selection = GetSelection();
+ bool findInFirstRangeStart = aDirection == eDirNext;
+
+ if (findInFirstRangeStart) {
+ range = selection->GetRangeAt(0);
+ startNode = range->GetStartParent();
+ endNode = range->GetEndParent();
+ nodeOffset = range->StartOffset();
+ hint = CARET_ASSOCIATE_AFTER;
+ } else {
+ range = selection->GetRangeAt(selection->RangeCount() - 1);
+ startNode = range->GetEndParent();
+ endNode = range->GetStartParent();
+ nodeOffset = range->EndOffset();
+ hint = CARET_ASSOCIATE_BEFORE;
+ }
+
+ nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ nsIFrame* startFrame =
+ fs->GetFrameForNodeOffset(startContent, nodeOffset, hint, aOutOffset);
+
+ if (!startFrame) {
+ ErrorResult err;
+ RefPtr<TreeWalker> walker = mPresShell->GetDocument()->CreateTreeWalker(
+ *startNode, nsIDOMNodeFilter::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 (aOutNode) {
+ *aOutNode = startNode.get();
+ }
+ if (aOutNodeOffset) {
+ *aOutNodeOffset = nodeOffset;
+ }
+ }
+
+ return startFrame;
+}
+
+bool
+AccessibleCaretManager::RestrictCaretDraggingOffsets(
+ nsIFrame::ContentOffsets& aOffsets)
+{
+ if (!mPresShell) {
+ return false;
+ }
+
+ MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
+
+ nsDirection dir = mActiveCaret == mFirstCaret.get() ? eDirPrevious : eDirNext;
+ int32_t offset = 0;
+ nsINode* node = nullptr;
+ int32_t contentOffset = 0;
+ nsIFrame* frame =
+ GetFrameForFirstRangeStartOrLastRangeEnd(dir, &offset, &node, &contentOffset);
+
+ if (!frame) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+
+ // Compare the active caret's new position (aOffsets) to the inactive caret's
+ // position.
+ int32_t cmpToInactiveCaretPos =
+ nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
+ content, contentOffset);
+
+ // 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.
+ nsPeekOffsetStruct limit(eSelectCluster, dir, offset, nsPoint(0, 0), true, true,
+ false, false, false);
+ 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.
+ int32_t cmpToLimit =
+ nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
+ limit.mResultContent, limit.mContentOffset);
+
+ auto SetOffsetsToLimit = [&aOffsets, &limit] () {
+ aOffsets.content = limit.mResultContent;
+ aOffsets.offset = limit.mContentOffset;
+ aOffsets.secondaryOffset = limit.mContentOffset;
+ };
+
+ if (!sCaretsAllowDraggingAcrossOtherCaret) {
+ if ((mActiveCaret == mFirstCaret.get() && cmpToLimit == 1) ||
+ (mActiveCaret == mSecondCaret.get() && 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 == mFirstCaret.get()) {
+ // First caret was moved across the second caret. After making change
+ // to the selection, the user will drag the second caret.
+ mActiveCaret = mSecondCaret.get();
+ }
+ break;
+ case -1:
+ if (mActiveCaret == mSecondCaret.get()) {
+ // Second caret was moved across the first caret. After making change
+ // to the selection, the user will drag the first caret.
+ mActiveCaret = mFirstCaret.get();
+ }
+ 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)
+{
+ if (!mPresShell) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ if (!rootFrame) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsPoint point = AdjustDragBoundary(aPoint);
+
+ // Find out which content we point to
+ nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+ rootFrame, point,
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
+ if (!ptFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (!fs) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult result;
+ nsIFrame* newFrame = nullptr;
+ nsPoint newPoint;
+ nsPoint ptInFrame = point;
+ nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
+ result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame,
+ &newFrame, newPoint);
+ if (NS_FAILED(result) || !newFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool selectable;
+ newFrame->IsSelectable(&selectable, nullptr);
+ if (!selectable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame::ContentOffsets offsets =
+ newFrame->GetContentOffsetsFromPoint(newPoint);
+ if (offsets.IsNull()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Selection* selection = GetSelection();
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (GetCaretMode() == CaretMode::Selection &&
+ !RestrictCaretDraggingOffsets(offsets)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ClearMaintainedSelection();
+
+ nsIFrame* anchorFrame = nullptr;
+ selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
+
+ nsIFrame* scrollable =
+ nsLayoutUtils::GetClosestFrameOfType(anchorFrame, nsGkAtoms::scrollFrame);
+ nsWeakFrame weakScrollable = scrollable;
+ fs->HandleClick(offsets.content, offsets.StartOffset(), offsets.EndOffset(),
+ GetCaretMode() == CaretMode::Selection, false,
+ offsets.associate);
+ if (!weakScrollable.IsAlive()) {
+ return NS_OK;
+ }
+
+ // Scroll scrolled frame.
+ nsIScrollableFrame* saf = do_QueryFrame(scrollable);
+ nsIFrame* capturingFrame = saf->GetScrolledFrame();
+ nsPoint ptInScrolled = point;
+ nsLayoutUtils::TransformPoint(rootFrame, capturingFrame, ptInScrolled);
+ fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay);
+ return NS_OK;
+}
+
+nsRect
+AccessibleCaretManager::GetAllChildFrameRectsUnion(nsIFrame* aFrame) const
+{
+ 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 (nsIFrame::ChildListIterator lists(frame); !lists.IsDone(); lists.Next()) {
+ // Loop all children to union their scrollable overflow rect.
+ for (nsIFrame* child : lists.CurrentList()) {
+ nsRect childRect = child->GetScrollableOverflowRectRelativeToSelf();
+ 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 = 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 &&
+ !sCaretsAllowDraggingAcrossOtherCaret) {
+ // 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 == mFirstCaret.get()) {
+ nscoord dragDownBoundaryY = mSecondCaret->LogicalPosition().y;
+ if (dragDownBoundaryY > 0 && adjustedPoint.y > dragDownBoundaryY) {
+ adjustedPoint.y = dragDownBoundaryY;
+ }
+ } else {
+ nscoord dragUpBoundaryY = mFirstCaret->LogicalPosition().y;
+ if (adjustedPoint.y < dragUpBoundaryY) {
+ adjustedPoint.y = dragUpBoundaryY;
+ }
+ }
+ }
+
+ return adjustedPoint;
+}
+
+uint32_t
+AccessibleCaretManager::CaretTimeoutMs() const
+{
+ static bool added = false;
+ static uint32_t caretTimeoutMs = 0;
+
+ if (!added) {
+ Preferences::AddUintVarCache(&caretTimeoutMs,
+ "layout.accessiblecaret.timeout_ms");
+ added = true;
+ }
+
+ return caretTimeoutMs;
+}
+
+void
+AccessibleCaretManager::LaunchCaretTimeoutTimer()
+{
+ if (!mPresShell || !mCaretTimeoutTimer || CaretTimeoutMs() == 0 ||
+ GetCaretMode() != CaretMode::Cursor || mActiveCaret) {
+ return;
+ }
+
+ nsTimerCallbackFunc callback = [](nsITimer* aTimer, void* aClosure) {
+ auto self = static_cast<AccessibleCaretManager*>(aClosure);
+ if (self->GetCaretMode() == CaretMode::Cursor) {
+ self->HideCarets();
+ }
+ };
+
+ mCaretTimeoutTimer->InitWithFuncCallback(callback, this, CaretTimeoutMs(),
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+AccessibleCaretManager::CancelCaretTimeoutTimer()
+{
+ if (mCaretTimeoutTimer) {
+ mCaretTimeoutTimer->Cancel();
+ }
+}
+
+void
+AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
+{
+ if (!mPresShell) {
+ return;
+ }
+
+ FlushLayout();
+ if (IsTerminated()) {
+ return;
+ }
+
+ Selection* sel = GetSelection();
+ if (!sel) {
+ return;
+ }
+
+ nsIDocument* doc = mPresShell->GetDocument();
+ MOZ_ASSERT(doc);
+
+ CaretStateChangedEventInit init;
+ init.mBubbles = true;
+
+ const nsRange* range = sel->GetAnchorFocusRange();
+ nsINode* commonAncestorNode = nullptr;
+ if (range) {
+ commonAncestorNode = range->GetCommonAncestor();
+ }
+
+ if (!commonAncestorNode) {
+ commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
+ }
+
+ RefPtr<DOMRect> domRect = new DOMRect(ToSupports(doc));
+ nsRect rect = nsLayoutUtils::GetSelectionBoundingRect(sel);
+
+ nsIFrame* commonAncestorFrame = nullptr;
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+
+ if (commonAncestorNode && commonAncestorNode->IsContent()) {
+ commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
+ }
+
+ if (commonAncestorFrame && rootFrame) {
+ nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
+ nsRect clampedRect = nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame,
+ rect);
+ nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
+ domRect->SetLayoutRect(clampedRect);
+ init.mSelectionVisible = !clampedRect.IsEmpty();
+ } else {
+ domRect->SetLayoutRect(rect);
+ init.mSelectionVisible = true;
+ }
+
+ // 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 = mFirstCaret->IsLogicallyVisible() ||
+ mSecondCaret->IsLogicallyVisible();
+ init.mCaretVisuallyVisible = mFirstCaret->IsVisuallyVisible() ||
+ mSecondCaret->IsVisuallyVisible();
+ sel->Stringify(init.mSelectedTextContent);
+
+ RefPtr<CaretStateChangedEvent> event =
+ CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
+
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ AC_LOG("%s: reason %d, collapsed %d, caretVisible %d", __FUNCTION__,
+ init.mReason, init.mCollapsed, init.mCaretVisible);
+
+ (new AsyncEventDispatcher(doc, event))->RunDOMEventWhenSafe();
+}
+
+} // namespace mozilla
diff --git a/layout/base/AccessibleCaretManager.h b/layout/base/AccessibleCaretManager.h
new file mode 100644
index 000000000..1cfe85d01
--- /dev/null
+++ b/layout/base/AccessibleCaretManager.h
@@ -0,0 +1,347 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsCOMPtr.h"
+#include "nsCoord.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIFrame.h"
+#include "nsISelectionListener.h"
+#include "mozilla/RefPtr.h"
+#include "nsWeakReference.h"
+#include "mozilla/dom/CaretStateChangedEvent.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+
+class nsFrameSelection;
+class nsIContent;
+class nsIPresShell;
+struct nsPoint;
+
+namespace mozilla {
+
+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.
+//
+// Please see the wiki page for more information.
+// https://wiki.mozilla.org/AccessibleCaret
+//
+class AccessibleCaretManager
+{
+public:
+ explicit AccessibleCaretManager(nsIPresShell* aPresShell);
+ virtual ~AccessibleCaretManager();
+
+ // 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.
+ virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);
+
+ // Drag caret to the given point. It's required to call PressCaret()
+ // beforehand.
+ virtual nsresult DragCaret(const nsPoint& aPoint);
+
+ // Release caret from he previous press action. It's required to call
+ // PressCaret() beforehand.
+ virtual nsresult ReleaseCaret();
+
+ // A quick single tap on caret on given point without dragging.
+ virtual nsresult TapCaret(const nsPoint& aPoint);
+
+ // Select a word or bring up paste shortcut (if Gaia is listening) under the
+ // given point.
+ virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);
+
+ // Handle scroll-start event.
+ virtual void OnScrollStart();
+
+ // Handle scroll-end event.
+ virtual void OnScrollEnd();
+
+ // Handle ScrollPositionChanged from nsIScrollObserver. This might be called
+ // at anytime, not necessary between OnScrollStart and OnScrollEnd.
+ virtual void OnScrollPositionChanged();
+
+ // Handle reflow event from nsIReflowObserver.
+ virtual void OnReflow();
+
+ // Handle blur event from nsFocusManager.
+ virtual void OnBlur();
+
+ // Handle NotifySelectionChanged event from nsISelectionListener.
+ virtual nsresult OnSelectionChanged(nsIDOMDocument* aDoc,
+ nsISelection* aSel,
+ int16_t aReason);
+ // Handle key event.
+ virtual void OnKeyboardEvent();
+
+ // The canvas frame holding the accessible caret anonymous content elements
+ // was reconstructed, resulting in the content elements getting cloned.
+ virtual void OnFrameReconstruction();
+
+ // 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);
+
+protected:
+ // 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 timeout, do not change its
+ // appearance to Normal.
+ RespectOldAppearance
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const UpdateCaretsHint& aResult);
+
+ // 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.
+ void UpdateCarets(UpdateCaretsHint aHint = UpdateCaretsHint::Default);
+
+ // Force hiding all carets regardless of the current selection status.
+ void HideCarets();
+
+ void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
+ void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);
+
+ // 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.
+ void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const;
+
+ nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
+ void SetSelectionDragState(bool aState) const;
+
+ // Called to extend a selection if possible that it's a phone number.
+ void SelectMoreIfPhoneNumber() const;
+ // Extend the current phone number selection in the requested direction.
+ 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 node and the node offset. Otherwise, get the frame
+ // and offset for the range end in the last range instead.
+ nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
+ nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode = nullptr,
+ int32_t* aOutNodeOffset = nullptr) const;
+
+ nsresult DragCaretInternal(const nsPoint& aPoint);
+ nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
+ void ClearMaintainedSelection() const;
+
+ // Caller is responsible to use IsTerminated() to check whether PresShell is
+ // still valid.
+ void FlushLayout() const;
+
+ dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const;
+ dom::Selection* GetSelection() const;
+ already_AddRefed<nsFrameSelection> GetFrameSelection() 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.
+ nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const;
+
+ // 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);
+
+ // Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
+ // no one touches it.
+ uint32_t CaretTimeoutMs() const;
+ void LaunchCaretTimeoutTimer();
+ void CancelCaretTimeoutTimer();
+
+ // ---------------------------------------------------------------------------
+ // The following functions are made virtual for stubbing or mocking in gtest.
+ //
+ // @return true if Terminate() had been called.
+ virtual bool IsTerminated() const { return !mPresShell; }
+
+ // 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.
+ virtual void UpdateCaretsForOverlappingTilt();
+
+ // Make the two carets always tilt.
+ virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
+ 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.
+ virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
+
+ // ---------------------------------------------------------------------------
+ // 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.
+ nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
+
+ // First caret is attached to nsCaret in cursor mode, and is attached to
+ // selection highlight as the left caret in selection mode.
+ UniquePtr<AccessibleCaret> mFirstCaret;
+
+ // Second caret is used solely in selection mode, and is attached to selection
+ // highlight as the right caret.
+ UniquePtr<AccessibleCaret> mSecondCaret;
+
+ // The caret being pressed or dragged.
+ AccessibleCaret* mActiveCaret = nullptr;
+
+ // The timer for hiding the caret in cursor mode after timeout behind the
+ // preference "layout.accessiblecaret.timeout_ms".
+ nsCOMPtr<nsITimer> mCaretTimeoutTimer;
+
+ // The caret mode since last update carets.
+ CaretMode mLastUpdateCaretMode = CaretMode::None;
+
+ // Store the appearance of the carets when calling OnScrollStart() so that it
+ // can be restored in OnScrollEnd().
+ AccessibleCaret::Appearance mFirstCaretAppearanceOnScrollStart =
+ AccessibleCaret::Appearance::None;
+ AccessibleCaret::Appearance mSecondCaretAppearanceOnScrollStart =
+ AccessibleCaret::Appearance::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 = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
+ 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;
+
+ // Preference to show selection bars at the two ends in selection mode. The
+ // selection bar is always disabled in cursor mode.
+ static bool sSelectionBarEnabled;
+
+ // Preference to allow smarter selection of phone numbers,
+ // when user long presses text to start.
+ static bool sExtendSelectionForPhoneNumber;
+
+ // Preference to show caret in cursor mode when long tapping on an empty
+ // content. This also changes the default update behavior in cursor mode,
+ // which is based on the emptiness of the content, into something more
+ // heuristic. See UpdateCaretsForCursorMode() for the details.
+ static bool sCaretShownWhenLongTappingOnEmptyContent;
+
+ // Preference to make carets always tilt in selection mode. By default, the
+ // carets become tilt only when they are overlapping.
+ static bool sCaretsAlwaysTilt;
+
+ // Preference to allow carets always show when scrolling (either panning or
+ // zooming) the page. When set to false, carets will hide during scrolling,
+ // and show again after the user lifts the finger off the screen.
+ static bool sCaretsAlwaysShowWhenScrolling;
+
+ // By default, javascript content selection changes closes AccessibleCarets and
+ // UI interactions. Optionally, we can try to maintain the active UI, keeping
+ // carets and ActionBar available.
+ static bool sCaretsScriptUpdates;
+
+ // Preference to allow one caret to be dragged across the other caret without
+ // any limitation. When set to false, one caret cannot be dragged across the
+ // other one.
+ static bool sCaretsAllowDraggingAcrossOtherCaret;
+
+ // AccessibleCaret pref for haptic feedback behaviour on longPress.
+ static bool sHapticFeedback;
+
+ // Preference to keep carets hidden when the selection is being manipulated
+ // by mouse input (as opposed to touch/pen/etc.).
+ static bool sHideCaretsForMouseInput;
+};
+
+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/ActiveLayerTracker.cpp b/layout/base/ActiveLayerTracker.cpp
new file mode 100644
index 000000000..4f60f82d7
--- /dev/null
+++ b/layout/base/ActiveLayerTracker.cpp
@@ -0,0 +1,555 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ActiveLayerTracker.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/PodOperations.h"
+#include "gfx2DGlue.h"
+#include "nsExpirationTracker.h"
+#include "nsContainerFrame.h"
+#include "nsIContent.h"
+#include "nsRefreshDriver.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsAnimationManager.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsTransitionManager.h"
+#include "nsDisplayList.h"
+#include "nsDOMCSSDeclaration.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+/**
+ * This tracks the state of a frame that may need active layers due to
+ * ongoing content changes or style changes that indicate animation.
+ *
+ * When no changes of *any* kind are detected after 75-100ms we remove this
+ * object. Because we only track all kinds of activity with a single
+ * nsExpirationTracker, it's possible a frame might remain active somewhat
+ * spuriously if different kinds of changes kept happening, but that almost
+ * certainly doesn't matter.
+ */
+class LayerActivity {
+public:
+ enum ActivityIndex {
+ ACTIVITY_OPACITY,
+ ACTIVITY_TRANSFORM,
+ ACTIVITY_LEFT,
+ ACTIVITY_TOP,
+ ACTIVITY_RIGHT,
+ ACTIVITY_BOTTOM,
+ ACTIVITY_MARGIN_LEFT,
+ ACTIVITY_MARGIN_TOP,
+ ACTIVITY_MARGIN_RIGHT,
+ ACTIVITY_MARGIN_BOTTOM,
+ ACTIVITY_BACKGROUND_POSITION,
+
+ ACTIVITY_SCALE,
+
+ // keep as last item
+ ACTIVITY_COUNT
+ };
+
+ explicit LayerActivity(nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mContent(nullptr)
+ , mContentActive(false)
+ {
+ PodArrayZero(mRestyleCounts);
+ }
+ ~LayerActivity();
+ nsExpirationState* GetExpirationState() { return &mState; }
+ uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty)
+ {
+ return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
+ }
+
+ static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty)
+ {
+ switch (aProperty) {
+ case eCSSProperty_opacity: return ACTIVITY_OPACITY;
+ case eCSSProperty_transform: return ACTIVITY_TRANSFORM;
+ case eCSSProperty_left: return ACTIVITY_LEFT;
+ case eCSSProperty_top: return ACTIVITY_TOP;
+ case eCSSProperty_right: return ACTIVITY_RIGHT;
+ case eCSSProperty_bottom: return ACTIVITY_BOTTOM;
+ case eCSSProperty_margin_left: return ACTIVITY_MARGIN_LEFT;
+ case eCSSProperty_margin_top: return ACTIVITY_MARGIN_TOP;
+ case eCSSProperty_margin_right: return ACTIVITY_MARGIN_RIGHT;
+ case eCSSProperty_margin_bottom: return ACTIVITY_MARGIN_BOTTOM;
+ case eCSSProperty_background_position: return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_x: return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_y: return ACTIVITY_BACKGROUND_POSITION;
+ default: MOZ_ASSERT(false); return ACTIVITY_OPACITY;
+ }
+ }
+
+ // While tracked, exactly one of mFrame or mContent is non-null, depending
+ // on whether this property is stored on a frame or on a content node.
+ // When this property is expired by the layer activity tracker, both mFrame
+ // and mContent are nulled-out and the property is deleted.
+ nsIFrame* mFrame;
+ nsIContent* mContent;
+
+ nsExpirationState mState;
+
+ // Previous scale due to the CSS transform property.
+ Maybe<gfxSize> mPreviousTransformScale;
+
+ // The scroll frame during for which we most recently received a call to
+ // NotifyAnimatedFromScrollHandler.
+ nsWeakFrame mAnimatingScrollHandlerFrame;
+ // The set of activities that were triggered during
+ // mAnimatingScrollHandlerFrame's scroll event handler.
+ EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
+
+ // Number of restyle operations detected
+ uint8_t mRestyleCounts[ACTIVITY_COUNT];
+ bool mContentActive;
+};
+
+class LayerActivityTracker final : public nsExpirationTracker<LayerActivity,4> {
+public:
+ // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
+ enum { GENERATION_MS = 100 };
+ LayerActivityTracker()
+ : nsExpirationTracker<LayerActivity,4>(GENERATION_MS,
+ "LayerActivityTracker")
+ , mDestroying(false)
+ {}
+ ~LayerActivityTracker() {
+ mDestroying = true;
+ AgeAllGenerations();
+ }
+
+ virtual void NotifyExpired(LayerActivity* aObject);
+
+public:
+ nsWeakFrame mCurrentScrollHandlerFrame;
+
+private:
+ bool mDestroying;
+};
+
+static LayerActivityTracker* gLayerActivityTracker = nullptr;
+
+LayerActivity::~LayerActivity()
+{
+ if (mFrame || mContent) {
+ NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
+ gLayerActivityTracker->RemoveObject(this);
+ }
+}
+
+// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
+
+void
+LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
+{
+ if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
+ // Reset the restyle counts, but let the layer activity survive.
+ PodArrayZero(aObject->mRestyleCounts);
+ MarkUsed(aObject);
+ return;
+ }
+
+ RemoveObject(aObject);
+
+ nsIFrame* f = aObject->mFrame;
+ nsIContent* c = aObject->mContent;
+ aObject->mFrame = nullptr;
+ aObject->mContent = nullptr;
+
+ MOZ_ASSERT((f == nullptr) != (c == nullptr),
+ "A LayerActivity object should always have a reference to either its frame or its content");
+
+ if (f) {
+ // The pres context might have been detached during the delay -
+ // that's fine, just skip the paint.
+ if (f->PresContext()->GetContainerWeak()) {
+ f->SchedulePaint();
+ }
+ f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ f->Properties().Delete(LayerActivityProperty());
+ } else {
+ c->DeleteProperty(nsGkAtoms::LayerActivity);
+ }
+}
+
+static LayerActivity*
+GetLayerActivity(nsIFrame* aFrame)
+{
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return nullptr;
+ }
+ FrameProperties properties = aFrame->Properties();
+ return properties.Get(LayerActivityProperty());
+}
+
+static LayerActivity*
+GetLayerActivityForUpdate(nsIFrame* aFrame)
+{
+ FrameProperties properties = aFrame->Properties();
+ LayerActivity* layerActivity = properties.Get(LayerActivityProperty());
+ if (layerActivity) {
+ gLayerActivityTracker->MarkUsed(layerActivity);
+ } else {
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker = new LayerActivityTracker();
+ }
+ layerActivity = new LayerActivity(aFrame);
+ gLayerActivityTracker->AddObject(layerActivity);
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ properties.Set(LayerActivityProperty(), layerActivity);
+ }
+ return layerActivity;
+}
+
+static void
+IncrementMutationCount(uint8_t* aCount)
+{
+ *aCount = uint8_t(std::min(0xFF, *aCount + 1));
+}
+
+/* static */ void
+ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent)
+{
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return;
+ }
+ FrameProperties properties = aFrame->Properties();
+ LayerActivity* layerActivity = properties.Remove(LayerActivityProperty());
+ aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mFrame = nullptr;
+ layerActivity->mContent = aContent;
+ aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
+ nsINode::DeleteProperty<LayerActivity>, true);
+}
+
+/* static */ void
+ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = static_cast<LayerActivity*>(
+ aContent->UnsetProperty(nsGkAtoms::LayerActivity));
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mContent = nullptr;
+ layerActivity->mFrame = aFrame;
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ aFrame->Properties().Set(LayerActivityProperty(), layerActivity);
+}
+
+static void
+IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
+{
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ if (!display->mSpecifiedTransform) {
+ // The transform was removed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ // Compute the new scale due to the CSS transform property.
+ nsPresContext* presContext = aFrame->PresContext();
+ RuleNodeCacheConditions dummy;
+ bool dummyBool;
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
+ Matrix4x4 transform =
+ nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
+ aFrame->StyleContext(),
+ presContext,
+ dummy, refBox,
+ presContext->AppUnitsPerCSSPixel(),
+ &dummyBool);
+ Matrix transform2D;
+ if (!transform.Is2D(&transform2D)) {
+ // We don't attempt to handle 3D transforms; just assume the scale changed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ gfxSize scale = ThebesMatrix(transform2D).ScaleFactors(true);
+ if (aActivity->mPreviousTransformScale == Some(scale)) {
+ return; // Nothing changed.
+ }
+
+ aActivity->mPreviousTransformScale = Some(scale);
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ IncrementMutationCount(&mutationCount);
+
+ if (aProperty == eCSSProperty_transform) {
+ IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
+ }
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]);
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ if (mutationCount != 0xFF) {
+ nsAutoString oldValue;
+ aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
+ if (aNewValue != oldValue) {
+ // We know this is animated, so just hack the mutation count.
+ mutationCount = 0xFF;
+ }
+ }
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ nsIFrame* aScrollFrame)
+{
+ if (aFrame->PresContext() != aScrollFrame->PresContext()) {
+ // Don't allow cross-document dependencies.
+ return;
+ }
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+
+ if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
+ // Discard any activity of a different scroll frame. We only track the
+ // most recent scroll handler induced activity.
+ layerActivity->mScrollHandlerInducedActivity.clear();
+ layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
+ }
+
+ layerActivity->mScrollHandlerInducedActivity += activityIndex;
+}
+
+static bool
+IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext)
+{
+ if (aPresContext->RefreshDriver()->IsInRefresh()) {
+ return true;
+ }
+ // Treat timeouts/setintervals as scripted animation callbacks for our
+ // purposes.
+ nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
+ return win && win->IsRunningTimeout();
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl)
+{
+ if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+ NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
+ }
+ if (gLayerActivityTracker &&
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
+ NotifyAnimatedFromScrollHandler(aFrame, aProperty,
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
+ }
+}
+
+/* static */ bool
+ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ return IsStyleAnimated(nullptr, aFrame, aProperty);
+}
+
+/* static */ bool
+ActiveLayerTracker::IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+{
+ return IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_x) ||
+ IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_y);
+}
+
+static bool
+CheckScrollInducedActivity(LayerActivity* aLayerActivity,
+ LayerActivity::ActivityIndex aActivityIndex,
+ nsDisplayListBuilder* aBuilder)
+{
+ if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
+ !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
+ return false;
+ }
+
+ nsIScrollableFrame* scrollFrame =
+ do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
+ if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
+ return true;
+ }
+
+ // The scroll frame has been destroyed or has become inactive. Clear it from
+ // the layer activity so that it can expire.
+ aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
+ aLayerActivity->mScrollHandlerInducedActivity.clear();
+ return false;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ // TODO: Add some abuse restrictions
+ if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) &&
+ aProperty == eCSSProperty_transform &&
+ (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return true;
+ }
+ if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
+ aProperty == eCSSProperty_opacity &&
+ (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return true;
+ }
+
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+ if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
+ return true;
+ }
+ if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
+ return true;
+ }
+ }
+ if (aProperty == eCSSProperty_transform && aFrame->Combines3DTransformWithAncestors()) {
+ return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
+ }
+ return nsLayoutUtils::HasEffectiveAnimation(aFrame, aProperty);
+}
+
+/* static */ bool
+ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_LEFT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_TOP] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_RIGHT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_BOTTOM] >= 2) {
+ return true;
+ }
+ }
+ // We should also check for running CSS animations of these properties once
+ // bug 1009693 is fixed. Until that happens, layerization isn't useful for
+ // animations of these properties because we'll invalidate the layer contents
+ // on every change anyway.
+ // See bug 1151346 for a patch that adds a check for CSS animations.
+ return false;
+}
+
+// A helper function for IsScaleSubjectToAnimation which returns true if the
+// given EffectSet contains a current effect that animates scale.
+static bool
+ContainsAnimatedScale(EffectSet& aEffects, nsIFrame* aFrame)
+{
+ for (dom::KeyframeEffectReadOnly* effect : aEffects) {
+ if (!effect->IsCurrent()) {
+ continue;
+ }
+
+ for (const AnimationProperty& prop : effect->Properties()) {
+ if (prop.mProperty != eCSSProperty_transform) {
+ continue;
+ }
+ for (AnimationPropertySegment segment : prop.mSegments) {
+ gfxSize from = segment.mFromValue.GetScaleValue(aFrame);
+ if (from != gfxSize(1.0f, 1.0f)) {
+ return true;
+ }
+ gfxSize to = segment.mToValue.GetScaleValue(aFrame);
+ if (to != gfxSize(1.0f, 1.0f)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame)
+{
+ // Check whether JavaScript is animating this frame's scale.
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity && layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
+ return true;
+ }
+
+ // Check if any animations, transitions, etc. associated with this frame may
+ // animate its scale.
+ EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+ if (effects && ContainsAnimatedScale(*effects, aFrame)) {
+ return true;
+ }
+
+ return false;
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ layerActivity->mContentActive = true;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsContentActive(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ return layerActivity && layerActivity->mContentActive;
+}
+
+/* static */ void
+ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame)
+{
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker = new LayerActivityTracker();
+ }
+ gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
+}
+
+/* static */ void
+ActiveLayerTracker::Shutdown()
+{
+ delete gLayerActivityTracker;
+ gLayerActivityTracker = nullptr;
+}
+
+} // namespace mozilla
diff --git a/layout/base/ActiveLayerTracker.h b/layout/base/ActiveLayerTracker.h
new file mode 100644
index 000000000..145f5481e
--- /dev/null
+++ b/layout/base/ActiveLayerTracker.h
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ACTIVELAYERTRACKER_H_
+#define ACTIVELAYERTRACKER_H_
+
+#include "nsCSSPropertyID.h"
+
+class nsIFrame;
+class nsIContent;
+class nsDisplayListBuilder;
+class nsDOMCSSDeclaration;
+
+namespace mozilla {
+
+/**
+ * This class receives various notifications about style changes and content
+ * changes that affect layerization decisions, and implements the heuristics
+ * that drive those decisions. It manages per-frame state to support those
+ * heuristics.
+ */
+class ActiveLayerTracker {
+public:
+ static void Shutdown();
+
+ /*
+ * We track style changes to selected styles:
+ * eCSSProperty_transform
+ * eCSSProperty_opacity
+ * eCSSProperty_left, eCSSProperty_top, eCSSProperty_right, eCSSProperty_bottom
+ * and use that information to guess whether style changes are animated.
+ */
+
+ /**
+ * Notify aFrame's style property as having changed due to a restyle,
+ * and therefore possibly wanting an active layer to render that style.
+ * Any such marking will time out after a short period.
+ * @param aProperty the property that has changed
+ */
+ static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty);
+ /**
+ * Notify aFrame's left/top/right/bottom properties as having (maybe)
+ * changed due to a restyle, and therefore possibly wanting an active layer
+ * to render that style. Any such marking will time out after a short period.
+ */
+ static void NotifyOffsetRestyle(nsIFrame* aFrame);
+ /**
+ * Mark aFrame as being known to have an animation of aProperty.
+ * Any such marking will time out after a short period.
+ * aNewValue and aDOMCSSDecl are used to determine whether the property's
+ * value has changed.
+ */
+ static void NotifyAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl);
+ /**
+ * Notify aFrame as being known to have an animation of aProperty through an
+ * inline style modification during aScrollFrame's scroll event handler.
+ */
+ static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ nsIFrame* aScrollFrame);
+ /**
+ * Notify that a property in the inline style rule of aFrame's element
+ * has been modified.
+ * This notification is incomplete --- not all modifications to inline
+ * style will trigger this.
+ * aNewValue and aDOMCSSDecl are used to determine whether the property's
+ * value has changed.
+ */
+ static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl);
+ /**
+ * Return true if aFrame's aProperty style should be considered as being animated
+ * for pre-rendering.
+ */
+ static bool IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty);
+ /**
+ * Return true if aFrame's aProperty style should be considered as being animated
+ * for constructing active layers.
+ */
+ static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+ /**
+ * Return true if any of aFrame's offset property styles should be considered
+ * as being animated for constructing active layers.
+ */
+ static bool IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame);
+ /**
+ * Return true if aFrame's background-position-x or background-position-y
+ * property is animated.
+ */
+ static bool IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+ /**
+ * Return true if aFrame either has an animated scale now, or is likely to
+ * have one in the future because it has a CSS animation or transition
+ * (which may not be playing right now) that affects its scale.
+ */
+ static bool IsScaleSubjectToAnimation(nsIFrame* aFrame);
+
+ /**
+ * Transfer the LayerActivity property to the frame's content node when the
+ * frame is about to be destroyed so that layer activity can be tracked
+ * throughout reframes of an element. Only call this when aFrame is the
+ * primary frame of aContent.
+ */
+ static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent);
+ /**
+ * Transfer the LayerActivity property back to the content node's primary
+ * frame after the frame has been created.
+ */
+ static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame);
+
+ /*
+ * We track modifications to the content of certain frames (i.e. canvas frames)
+ * and use that to make layering decisions.
+ */
+
+ /**
+ * Mark aFrame's content as being active. This marking will time out after
+ * a short period.
+ */
+ static void NotifyContentChange(nsIFrame* aFrame);
+ /**
+ * Return true if this frame's content is still marked as active.
+ */
+ static bool IsContentActive(nsIFrame* aFrame);
+
+ /**
+ * Called before and after a scroll event handler is executed, with the
+ * scrollframe or nullptr, respectively. This acts as a hint to treat
+ * inline style changes during the handler differently.
+ */
+ static void SetCurrentScrollHandlerFrame(nsIFrame* aFrame);
+};
+
+} // namespace mozilla
+
+#endif /* ACTIVELAYERTRACKER_H_ */
diff --git a/layout/base/ArenaObjectID.h b/layout/base/ArenaObjectID.h
new file mode 100644
index 000000000..db8dfe872
--- /dev/null
+++ b/layout/base/ArenaObjectID.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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 {
+ eArenaObjectID_DummyBeforeFirstObjectID = nsQueryFrame::NON_FRAME_MARKER - 1,
+
+#define PRES_ARENA_OBJECT(name_) \
+ eArenaObjectID_##name_,
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT
+
+ /**
+ * The PresArena implementation uses this bit to distinguish objects
+ * allocated by size from objects allocated by type ID (that is, frames
+ * using AllocateByFrameID and other objects using AllocateByObjectID).
+ * It should not collide with any Object ID (above) or frame ID (in
+ * nsQueryFrame.h). It is not 0x80000000 to avoid the question of
+ * whether enumeration constants are signed.
+ */
+ eArenaObjectID_NON_OBJECT_MARKER = 0x40000000
+};
+
+};
+
+#endif
diff --git a/layout/base/ArenaRefPtr.h b/layout/base/ArenaRefPtr.h
new file mode 100644
index 000000000..bc8ecaa70
--- /dev/null
+++ b/layout/base/ArenaRefPtr.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/* smart pointer for strong references to nsPresArena-allocated objects
+ that might be held onto until the arena's destruction */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+
+#ifndef mozilla_ArenaRefPtr_h
+#define mozilla_ArenaRefPtr_h
+
+class nsPresArena;
+
+namespace mozilla {
+
+/**
+ * A class for holding strong references to nsPresArena-allocated
+ * objects.
+ *
+ * Since the arena's lifetime is not related to the refcounts
+ * of the objects allocated within it, it is possible to have a strong
+ * reference to an arena-allocated object that lives until the
+ * destruction of the arena. An ArenaRefPtr acts like a weak reference
+ * in that it will clear its referent if the arena is about to go away.
+ *
+ * T must be a class that has these two methods:
+ *
+ * static mozilla::ArenaObjectID ArenaObjectID();
+ * U* Arena();
+ *
+ * where U is a class that has these two methods:
+ *
+ * void RegisterArenaRefPtr(ArenaRefPtr<T>*);
+ * void DeregisterArenaRefPtr(ArenaRefPtr<T>*);
+ *
+ * Currently, both nsPresArena and nsIPresShell can be used as U.
+ *
+ * The ArenaObjectID method must return the mozilla::ArenaObjectID that
+ * uniquely identifies T, and the Arena method must return the nsPresArena
+ * (or a proxy for it) in which the object was allocated.
+ */
+template<typename T>
+class ArenaRefPtr
+{
+ friend class ::nsPresArena;
+
+public:
+ ArenaRefPtr()
+ {
+ AssertValidType();
+ }
+
+ template<typename I>
+ MOZ_IMPLICIT ArenaRefPtr(already_AddRefed<I>& aRhs)
+ {
+ AssertValidType();
+ assign(aRhs);
+ }
+
+ template<typename I>
+ MOZ_IMPLICIT ArenaRefPtr(already_AddRefed<I>&& aRhs)
+ {
+ AssertValidType();
+ assign(aRhs);
+ }
+
+ MOZ_IMPLICIT ArenaRefPtr(T* aRhs)
+ {
+ AssertValidType();
+ assign(aRhs);
+ }
+
+ template<typename I>
+ ArenaRefPtr<T>& operator=(already_AddRefed<I>& aRhs)
+ {
+ assign(aRhs);
+ return *this;
+ }
+
+ template<typename I>
+ ArenaRefPtr<T>& operator=(already_AddRefed<I>&& aRhs)
+ {
+ assign(aRhs);
+ return *this;
+ }
+
+ ArenaRefPtr<T>& operator=(T* aRhs)
+ {
+ assign(aRhs);
+ return *this;
+ }
+
+ ~ArenaRefPtr() { assign(nullptr); }
+
+#ifdef MOZ_HAVE_REF_QUALIFIERS
+ operator T*() const & { return get(); }
+ operator T*() const && = delete;
+ explicit operator bool() const { return !!mPtr; }
+ bool operator!() const { return !mPtr; }
+#else
+ operator T*() const { return get(); }
+#endif
+
+ T* operator->() const { return mPtr.operator->(); }
+ T& operator*() const { return *get(); }
+
+ T* get() const { return mPtr; }
+
+private:
+ void AssertValidType();
+
+ /**
+ * Clears the pointer to the arena-allocated object but skips the usual
+ * step of deregistering the ArenaRefPtr from the nsPresArena. This
+ * method is called by nsPresArena when clearing all registered ArenaRefPtrs
+ * so that it can deregister them all at once, avoiding hash table churn.
+ */
+ void ClearWithoutDeregistering()
+ {
+ mPtr = nullptr;
+ }
+
+ template<typename I>
+ void assign(already_AddRefed<I>& aSmartPtr)
+ {
+ RefPtr<T> newPtr(aSmartPtr);
+ assignFrom(newPtr);
+ }
+
+ template<typename I>
+ void assign(already_AddRefed<I>&& aSmartPtr)
+ {
+ RefPtr<T> newPtr(aSmartPtr);
+ assignFrom(newPtr);
+ }
+
+ void assign(T* aPtr) { assignFrom(aPtr); }
+
+ template<typename I>
+ void assignFrom(I& aPtr)
+ {
+ if (aPtr == mPtr) {
+ return;
+ }
+ bool sameArena = mPtr && aPtr && mPtr->Arena() == aPtr->Arena();
+ if (mPtr && !sameArena) {
+ MOZ_ASSERT(mPtr->Arena());
+ mPtr->Arena()->DeregisterArenaRefPtr(this);
+ }
+ mPtr = Move(aPtr);
+ if (mPtr && !sameArena) {
+ MOZ_ASSERT(mPtr->Arena());
+ mPtr->Arena()->RegisterArenaRefPtr(this);
+ }
+ }
+
+ RefPtr<T> mPtr;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ArenaRefPtr_h
diff --git a/layout/base/ArenaRefPtrInlines.h b/layout/base/ArenaRefPtrInlines.h
new file mode 100644
index 000000000..a85e98569
--- /dev/null
+++ b/layout/base/ArenaRefPtrInlines.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/* inline methods that belong in ArenaRefPtr.h, except that they require
+ the inclusion of headers for all types that ArenaRefPtr can handle */
+
+#ifndef mozilla_ArenaRefPtrInlines_h
+#define mozilla_ArenaRefPtrInlines_h
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/Assertions.h"
+#include "nsStyleStruct.h"
+
+namespace mozilla {
+
+template<typename T>
+void
+ArenaRefPtr<T>::AssertValidType()
+{
+#ifdef DEBUG
+ bool ok =
+#define PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(name_) \
+ T::ArenaObjectID() == eArenaObjectID_##name_ ||
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+ false;
+ MOZ_ASSERT(ok, "ArenaRefPtr<T> template parameter T must be declared in "
+ "nsPresArenaObjectList with "
+ "PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT");
+#endif
+}
+
+} // namespace mozilla
+
+template<typename T>
+void
+nsPresArena::RegisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr)
+{
+ MOZ_ASSERT(!mArenaRefPtrs.Contains(aPtr));
+ mArenaRefPtrs.Put(aPtr, T::ArenaObjectID());
+}
+
+#endif
diff --git a/layout/base/BorderCache.h b/layout/base/BorderCache.h
new file mode 100644
index 000000000..c70acf2ad
--- /dev/null
+++ b/layout/base/BorderCache.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_BorderCache_h_
+#define mozilla_BorderCache_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/HashFunctions.h"
+#include "nsDataHashtable.h"
+#include "PLDHashTable.h"
+
+namespace mozilla {
+// Cache for best overlap and best dashLength.
+
+struct FourFloats
+{
+ typedef mozilla::gfx::Float Float;
+
+ Float n[4];
+
+ FourFloats()
+ {
+ n[0] = 0.0f;
+ n[1] = 0.0f;
+ n[2] = 0.0f;
+ n[3] = 0.0f;
+ }
+
+ FourFloats(Float a, Float b, Float c, Float d)
+ {
+ n[0] = a;
+ n[1] = b;
+ n[2] = c;
+ n[3] = d;
+ }
+
+ bool
+ operator==(const FourFloats& aOther) const
+ {
+ return n[0] == aOther.n[0] &&
+ n[1] == aOther.n[1] &&
+ n[2] == aOther.n[2] &&
+ n[3] == aOther.n[3];
+ }
+};
+
+class FourFloatsHashKey : public PLDHashEntryHdr
+{
+public:
+ typedef const FourFloats& KeyType;
+ typedef const FourFloats* KeyTypePointer;
+
+ explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ FourFloatsHashKey(const FourFloatsHashKey& aToCopy) : mValue(aToCopy.mValue) {}
+ ~FourFloatsHashKey() {}
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ {
+ return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+private:
+ const FourFloats mValue;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_BorderCache_h_ */
diff --git a/layout/base/BorderConsts.h b/layout/base/BorderConsts.h
new file mode 100644
index 000000000..14b937399
--- /dev/null
+++ b/layout/base/BorderConsts.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_BorderConsts_h_
+#define mozilla_BorderConsts_h_
+
+// thickness of dashed line relative to dotted line
+#define DOT_LENGTH 1 // square
+#define DASH_LENGTH 3 // 3 times longer than dot
+
+// some shorthand for side bits
+#define SIDE_BIT_TOP (1 << NS_SIDE_TOP)
+#define SIDE_BIT_RIGHT (1 << NS_SIDE_RIGHT)
+#define SIDE_BIT_BOTTOM (1 << NS_SIDE_BOTTOM)
+#define SIDE_BIT_LEFT (1 << NS_SIDE_LEFT)
+#define SIDE_BITS_ALL (SIDE_BIT_TOP|SIDE_BIT_RIGHT|SIDE_BIT_BOTTOM|SIDE_BIT_LEFT)
+
+#define C_TL NS_CORNER_TOP_LEFT
+#define C_TR NS_CORNER_TOP_RIGHT
+#define C_BR NS_CORNER_BOTTOM_RIGHT
+#define C_BL NS_CORNER_BOTTOM_LEFT
+
+#define BORDER_SEGMENT_COUNT_MAX 100
+#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000
+
+#endif /* mozilla_BorderConsts_h_ */
diff --git a/layout/base/CaretAssociationHint.h b/layout/base/CaretAssociationHint.h
new file mode 100644
index 000000000..9067de626
--- /dev/null
+++ b/layout/base/CaretAssociationHint.h
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CaretAssociationHint_h___
+#define CaretAssociationHint_h___
+
+namespace mozilla {
+
+/**
+ * Hint whether a caret is associated with the content before a
+ * given character offset (ASSOCIATE_BEFORE), or with the content after a given
+ * character offset (ASSOCIATE_AFTER).
+ */
+enum CaretAssociationHint {
+ CARET_ASSOCIATE_BEFORE,
+ CARET_ASSOCIATE_AFTER
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/DashedCornerFinder.cpp b/layout/base/DashedCornerFinder.cpp
new file mode 100644
index 000000000..cb58eb51e
--- /dev/null
+++ b/layout/base/DashedCornerFinder.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DashedCornerFinder.h"
+
+#include "mozilla/Move.h"
+#include "BorderCache.h"
+#include "BorderConsts.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+struct BestDashLength
+{
+ typedef mozilla::gfx::Float Float;
+
+ Float dashLength;
+ size_t count;
+
+ BestDashLength()
+ : dashLength(0.0f), count(0)
+ {}
+
+ BestDashLength(Float aDashLength, size_t aCount)
+ : dashLength(aDashLength), count(aCount)
+ {}
+};
+
+static const size_t DashedCornerCacheSize = 256;
+nsDataHashtable<FourFloatsHashKey, BestDashLength> DashedCornerCache;
+
+DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier,
+ const Bezier& aInnerBezier,
+ Float aBorderWidthH, Float aBorderWidthV,
+ const Size& aCornerDim)
+ : mOuterBezier(aOuterBezier),
+ mInnerBezier(aInnerBezier),
+ mLastOuterP(aOuterBezier.mPoints[0]), mLastInnerP(aInnerBezier.mPoints[0]),
+ mLastOuterT(0.0f), mLastInnerT(0.0f),
+ mBestDashLength(DOT_LENGTH * DASH_LENGTH),
+ mHasZeroBorderWidth(false), mHasMore(true),
+ mMaxCount(aCornerDim.width + aCornerDim.height),
+ mType(OTHER),
+ mI(0), mCount(0)
+{
+ NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f,
+ "At least one side should have non-zero width.");
+
+ DetermineType(aBorderWidthH, aBorderWidthV);
+
+ Reset();
+}
+
+void
+DashedCornerFinder::DetermineType(Float aBorderWidthH, Float aBorderWidthV)
+{
+ if (aBorderWidthH < aBorderWidthV) {
+ // Always draw from wider side to thinner side.
+ Swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
+ Swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
+ Swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
+ Swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
+ mLastOuterP = mOuterBezier.mPoints[0];
+ mLastInnerP = mInnerBezier.mPoints[0];
+ }
+
+ // See the comment at mType declaration for each condition.
+
+ Float borderRadiusA = fabs(mOuterBezier.mPoints[0].x -
+ mOuterBezier.mPoints[3].x);
+ Float borderRadiusB = fabs(mOuterBezier.mPoints[0].y -
+ mOuterBezier.mPoints[3].y);
+ if (aBorderWidthH == aBorderWidthV &&
+ borderRadiusA == borderRadiusB &&
+ borderRadiusA > aBorderWidthH * 2.0f) {
+ Float curveHeight = borderRadiusA - aBorderWidthH / 2.0;
+
+ mType = PERFECT;
+ Float borderLength = M_PI * curveHeight / 2.0f;
+
+ Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH;
+ size_t count = ceil(borderLength / dashWidth);
+ if (count % 2) {
+ count++;
+ }
+ mCount = count / 2 + 1;
+ mBestDashLength = borderLength / (aBorderWidthH * count);
+ }
+
+ Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV);
+ if (minBorderWidth == 0.0f) {
+ mHasZeroBorderWidth = true;
+ }
+
+ if (mType == OTHER && !mHasZeroBorderWidth) {
+ Float minBorderRadius = std::min(borderRadiusA, borderRadiusB);
+ Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB);
+ Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV);
+
+ FindBestDashLength(minBorderWidth, maxBorderWidth,
+ minBorderRadius, maxBorderRadius);
+ }
+}
+
+bool
+DashedCornerFinder::HasMore(void) const
+{
+ if (mHasZeroBorderWidth) {
+ return mI < mMaxCount && mHasMore;
+ }
+
+ return mI < mCount;
+}
+
+DashedCornerFinder::Result
+DashedCornerFinder::Next(void)
+{
+ Float lastOuterT, lastInnerT, outerT, innerT;
+
+ if (mI == 0) {
+ lastOuterT = 0.0f;
+ lastInnerT = 0.0f;
+ } else {
+ if (mType == PERFECT) {
+ lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f);
+ } else {
+ Float last2OuterT = mLastOuterT;
+ Float last2InnerT = mLastInnerT;
+
+ (void)FindNext(mBestDashLength);
+
+ //
+ // mLastOuterT lastOuterT
+ // | |
+ // v v
+ // +---+---+---+---+ <- last2OuterT
+ // | |###|###| |
+ // | |###|###| |
+ // | |###|###| |
+ // +---+---+---+---+ <- last2InnerT
+ // ^ ^
+ // | |
+ // mLastInnerT lastInnerT
+ lastOuterT = (mLastOuterT + last2OuterT) / 2.0f;
+ lastInnerT = (mLastInnerT + last2InnerT) / 2.0f;
+ }
+ }
+
+ if ((!mHasZeroBorderWidth && mI == mCount - 1) ||
+ (mHasZeroBorderWidth && !mHasMore)) {
+ outerT = 1.0f;
+ innerT = 1.0f;
+ } else {
+ if (mType == PERFECT) {
+ outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f);
+ } else {
+ Float last2OuterT = mLastOuterT;
+ Float last2InnerT = mLastInnerT;
+
+ (void)FindNext(mBestDashLength);
+
+ //
+ // outerT last2OuterT
+ // | |
+ // v v
+ // mLastOuterT -> +---+---+---+---+
+ // | |###|###| |
+ // | |###|###| |
+ // | |###|###| |
+ // mLastInnerT -> +---+---+---+---+
+ // ^ ^
+ // | |
+ // innerT last2InnerT
+ outerT = (mLastOuterT + last2OuterT) / 2.0f;
+ innerT = (mLastInnerT + last2InnerT) / 2.0f;
+ }
+ }
+
+ mI++;
+
+ Bezier outerSectionBezier;
+ Bezier innerSectionBezier;
+ GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT);
+ GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT);
+ return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier);
+}
+
+void
+DashedCornerFinder::Reset(void)
+{
+ mLastOuterP = mOuterBezier.mPoints[0];
+ mLastInnerP = mInnerBezier.mPoints[0];
+ mLastOuterT = 0.0f;
+ mLastInnerT = 0.0f;
+ mHasMore = true;
+}
+
+Float
+DashedCornerFinder::FindNext(Float dashLength)
+{
+ Float upper = 1.0f;
+ Float lower = mLastOuterT;
+
+ Point OuterP, InnerP;
+ // Start from upper bound to check if this is the last segment.
+ Float outerT = upper;
+ Float innerT;
+ Float W = 0.0f;
+ Float L = 0.0f;
+
+ const Float LENGTH_MARGIN = 0.1f;
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ OuterP = GetBezierPoint(mOuterBezier, outerT);
+ InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT);
+
+ // Calculate approximate dash length.
+ //
+ // W = (W1 + W2) / 2
+ // L = (OuterL + InnerL) / 2
+ // dashLength = L / W
+ //
+ // ____----+----____
+ // OuterP ___--- | ---___ mLastOuterP
+ // +--- | ---+
+ // | | |
+ // | | |
+ // | W | W1 |
+ // | | |
+ // W2 | | |
+ // | | ______------+
+ // | ____+---- mLastInnerP
+ // | ___---
+ // | __---
+ // +--
+ // InnerP
+ // OuterL
+ // ____---------____
+ // OuterP ___--- ---___ mLastOuterP
+ // +--- ---+
+ // | L |
+ // | ___----------______ |
+ // | __--- -----+
+ // | __-- |
+ // +-- |
+ // | InnerL ______------+
+ // | ____----- mLastInnerP
+ // | ___---
+ // | __---
+ // +--
+ // InnerP
+ Float W1 = (mLastOuterP - mLastInnerP).Length();
+ Float W2 = (OuterP - InnerP).Length();
+ Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT);
+ Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT);
+ W = (W1 + W2) / 2.0f;
+ L = (OuterL + InnerL) / 2.0f;
+ if (L > W * dashLength + LENGTH_MARGIN) {
+ if (i > 0) {
+ upper = outerT;
+ }
+ } else if (L < W * dashLength - LENGTH_MARGIN) {
+ if (i == 0) {
+ // This is the last segment with shorter dashLength.
+ mHasMore = false;
+ break;
+ }
+ lower = outerT;
+ } else {
+ break;
+ }
+
+ outerT = (upper + lower) / 2.0f;
+ }
+
+ mLastOuterP = OuterP;
+ mLastInnerP = InnerP;
+ mLastOuterT = outerT;
+ mLastInnerT = innerT;
+
+ if (W == 0.0f) {
+ return 1.0f;
+ }
+
+ return L / W;
+}
+
+void
+DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth,
+ Float aMaxBorderWidth,
+ Float aMinBorderRadius,
+ Float aMaxBorderRadius)
+{
+ // If dashLength is not calculateable, find it with binary search,
+ // such that there exists i that OuterP_i == OuterP_n and
+ // InnerP_i == InnerP_n with given dashLength.
+
+ FourFloats key(aMinBorderWidth, aMaxBorderWidth,
+ aMinBorderRadius, aMaxBorderRadius);
+ BestDashLength best;
+ if (DashedCornerCache.Get(key, &best)) {
+ mCount = best.count;
+ mBestDashLength = best.dashLength;
+ return;
+ }
+
+ Float lower = 1.0f;
+ Float upper = DOT_LENGTH * DASH_LENGTH;
+ Float dashLength = upper;
+ size_t targetCount = 0;
+
+ const Float LENGTH_MARGIN = 0.1f;
+ for (size_t j = 0; j < MAX_LOOP; j++) {
+ size_t count;
+ Float actualDashLength;
+ if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) {
+ if (j == 0) {
+ mCount = mMaxCount;
+ break;
+ }
+ }
+
+ if (j == 0) {
+ if (count == 1) {
+ // If only 1 segment fits, fill entire region
+ //
+ // count = 1
+ // mCount = 1
+ // | 1 |
+ // +---+---+
+ // |###|###|
+ // |###|###|
+ // |###|###|
+ // +---+---+
+ // 1
+ mCount = 1;
+ break;
+ }
+
+ // targetCount should be 2n.
+ //
+ // targetCount = 2
+ // mCount = 2
+ // | 1 | 2 |
+ // +---+---+---+---+
+ // |###| | |###|
+ // |###| | |###|
+ // |###| | |###|
+ // +---+---+---+---+
+ // 1 2
+ //
+ // targetCount = 6
+ // mCount = 4
+ // | 1 | 2 | 3 | 4 | 5 | 6 |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|###| | |###|
+ // |###| | |###|###| | |###|###| | |###|
+ // |###| | |###|###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+---+---+---+---+
+ // 1 2 3 4
+ if (count % 2) {
+ targetCount = count + 1;
+ } else {
+ targetCount = count;
+ }
+
+ mCount = targetCount / 2 + 1;
+ }
+
+ if (count == targetCount) {
+ mBestDashLength = dashLength;
+
+ // actualDashLength won't be greater than dashLength.
+ if (actualDashLength > dashLength - LENGTH_MARGIN) {
+ break;
+ }
+
+ // We started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ upper = dashLength;
+ }
+ } else {
+ // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
+ // and we started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (count > targetCount) {
+ lower = dashLength;
+ } else {
+ upper = dashLength;
+ }
+ }
+ }
+
+ dashLength = (upper + lower) / 2.0f;
+ }
+
+ if (DashedCornerCache.Count() > DashedCornerCacheSize) {
+ DashedCornerCache.Clear();
+ }
+ DashedCornerCache.Put(key, BestDashLength(mBestDashLength, mCount));
+}
+
+bool
+DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength,
+ size_t* aCount,
+ Float* aActualDashLength)
+{
+ // Return the number of segments and the last segment's dashLength for
+ // the given dashLength.
+
+ Reset();
+
+ for (size_t i = 0; i < mMaxCount; i++) {
+ Float actualDashLength = FindNext(aDashLength);
+ if (mLastOuterT >= 1.0f) {
+ *aCount = i + 1;
+ *aActualDashLength = actualDashLength;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/base/DashedCornerFinder.h b/layout/base/DashedCornerFinder.h
new file mode 100644
index 000000000..1fda9b6bd
--- /dev/null
+++ b/layout/base/DashedCornerFinder.h
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DashedCornerFinder_h_
+#define mozilla_DashedCornerFinder_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+
+namespace mozilla {
+
+// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that
+// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
+// where
+// OuterP_i: OuterCurve(OuterT_i)
+// InnerP_i: InnerCurve(OuterT_i)
+// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1}
+// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1}
+// W_i = |OuterP_i - InnerP_i|
+// 1.0 < dashLength < 3.0
+//
+// OuterP_1 OuterP_0
+// _+__-----------+ OuterCurve
+// OuterP_2 __---- | OuterL_1 |
+// __+--- | |
+// __--- | OuterL_2 | |
+// OuterP_3 _-- | | W_1 | W_0
+// _+ | | |
+// / \ W_2 | | |
+// / \ | | InnerL_1 |
+// | \ | InnerL_2|____-------+ InnerCurve
+// | \ |____----+ InnerP_0
+// | . \ __---+ InnerP_1
+// | \ / InnerP_2
+// | . /+ InnerP_3
+// | |
+// | . |
+// | |
+// | |
+// | |
+// OuterP_{n-1} +--------+ InnerP_{n-1}
+// | |
+// | |
+// | |
+// | |
+// | |
+// OuterP_n +--------+ InnerP_n
+//
+// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
+// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
+// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2),
+// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)],
+// to start and end with half segment.
+//
+// _+__----+------+ OuterCurve
+// _+---- | |######|
+// __+---#| | |######|
+// _+---##|####| | |######|
+// _-- |#####|#####| | |#####|
+// _+ |#####|#####| | |#####|
+// / \ |#####|####| | |#####|
+// / \ |####|#####| | |#####|
+// | \ |####|####| |____-+-----+ InnerCurve
+// | \ |####|____+---+
+// | . \ __+---+
+// | \ /
+// | . /+
+// | |
+// | . |
+// | |
+// | |
+// | |
+// +--------+
+// | |
+// | |
+// +--------+
+// |########|
+// |########|
+// +--------+
+
+class DashedCornerFinder
+{
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Size Size;
+
+public:
+ struct Result
+ {
+ // Control points for the outer curve and the inner curve.
+ //
+ // outerSectionBezier
+ // |
+ // v _+ 3
+ // ___---#|
+ // 0 +---#######|
+ // |###########|
+ // |###########|
+ // |##########|
+ // |##########|
+ // |#########|
+ // |#####____+ 3
+ // 0 +----
+ // ^
+ // |
+ // innerSectionBezier
+ Bezier outerSectionBezier;
+ Bezier innerSectionBezier;
+
+ Result(const Bezier& aOuterSectionBezier,
+ const Bezier& aInnerSectionBezier)
+ : outerSectionBezier(aOuterSectionBezier),
+ innerSectionBezier(aInnerSectionBezier)
+ {}
+ };
+
+ // aCornerDim.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+--
+ // ^ | __-- | ^
+ // | | _- | |
+ // | | / | | aBorderWidthH
+ // | | / | |
+ // | | | | v
+ // | | | __--+--
+ // aCornerDim.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | |
+ // --+---------+
+ // | |
+ // |<------->|
+ // aBorderWidthV
+ DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
+ Float aBorderWidthH, Float aBorderWidthV,
+ const Size& aCornerDim);
+
+ bool HasMore(void) const;
+ Result Next(void);
+
+private:
+ static const size_t MAX_LOOP = 32;
+
+ // Bezier control points for the outer curve and the inner curve.
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / |
+ // / |
+ // | |
+ // | __--+ inner curve
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +---------+
+ Bezier mOuterBezier;
+ Bezier mInnerBezier;
+
+ Point mLastOuterP;
+ Point mLastInnerP;
+ Float mLastOuterT;
+ Float mLastInnerT;
+
+ // Length for each segment, ratio of the border width at that point.
+ Float mBestDashLength;
+
+ // If one of border-widths is 0, do not calculate mBestDashLength, and draw
+ // segments until it reaches the other side or exceeds mMaxCount.
+ bool mHasZeroBorderWidth;
+ bool mHasMore;
+
+ // The maximum number of segments.
+ size_t mMaxCount;
+
+ enum {
+ // radius.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+--
+ // ^ | __-- | ^
+ // | | _- | |
+ // | | / + | top-width
+ // | | / | |
+ // | | | | v
+ // | | | __--+--
+ // radius.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | |
+ // --+----+----+
+ // | |
+ // |<------->|
+ // left-width
+
+ // * top-width == left-width
+ // * radius.width == radius.height
+ // * top-width < radius.width * 2
+ //
+ // Split the perfect circle's arc into 2n segments, each segment's length is
+ // top-width * dashLength. Then split the inner curve and the outer curve
+ // with same angles.
+ //
+ // radius.width
+ // |<---------------------->|
+ // | | v
+ // --+------------------------+--
+ // ^ | | | top-width / 2
+ // | | perfect | |
+ // | | circle's ___---+--
+ // | | arc __-+ | ^
+ // | | | _- | |
+ // radius.height | | | + | +--
+ // | | | / \ | |
+ // | | | | \ | |
+ // | | | | \ | |
+ // | | +->| \ | |
+ // | | +---__ \ | |
+ // | | | --__ \ | |
+ // | | | ---__ \ | |
+ // v | | --_\||
+ // --+----+----+--------------+
+ // | | |
+ // |<-->| |
+ // left-width / 2
+ PERFECT,
+
+ // Other cases.
+ //
+ // Split the outer curve and the inner curve into 2n segments, each segment
+ // satisfies following:
+ // (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
+ OTHER
+ } mType;
+
+ size_t mI;
+ size_t mCount;
+
+ // Determine mType from parameters.
+ void DetermineType(Float aBorderWidthH, Float aBorderWidthV);
+
+ // Reset calculation.
+ void Reset(void);
+
+ // Find next segment.
+ Float FindNext(Float dashLength);
+
+ // Find mBestDashLength for parameters.
+ void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth,
+ Float aMinBorderRadius, Float aMaxBorderRadius);
+
+ // Fill corner with dashes with given dash length, and return the number of
+ // segments and last segment's dash length.
+ bool GetCountAndLastDashLength(Float aDashLength,
+ size_t* aCount, Float* aActualDashLength);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_DashedCornerFinder_h_ */
diff --git a/layout/base/DisplayItemClip.cpp b/layout/base/DisplayItemClip.cpp
new file mode 100644
index 000000000..ee4e19e5e
--- /dev/null
+++ b/layout/base/DisplayItemClip.cpp
@@ -0,0 +1,476 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayItemClip.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsPresContext.h"
+#include "nsCSSRendering.h"
+#include "nsLayoutUtils.h"
+#include "nsRegion.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+void
+DisplayItemClip::SetTo(const nsRect& aRect)
+{
+ SetTo(aRect, nullptr);
+}
+
+void
+DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii)
+{
+ mHaveClipRect = true;
+ mClipRect = aRect;
+ if (aRadii) {
+ mRoundedClipRects.SetLength(1);
+ mRoundedClipRects[0].mRect = aRect;
+ memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
+ } else {
+ mRoundedClipRects.Clear();
+ }
+}
+
+void
+DisplayItemClip::SetTo(const nsRect& aRect,
+ const nsRect& aRoundedRect,
+ const nscoord* aRadii)
+{
+ mHaveClipRect = true;
+ mClipRect = aRect;
+ mRoundedClipRects.SetLength(1);
+ mRoundedClipRects[0].mRect = aRoundedRect;
+ memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
+}
+
+bool
+DisplayItemClip::MayIntersect(const nsRect& aRect) const
+{
+ if (!mHaveClipRect) {
+ return !aRect.IsEmpty();
+ }
+ nsRect r = aRect.Intersect(mClipRect);
+ if (r.IsEmpty()) {
+ return false;
+ }
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ const RoundedRect& rr = mRoundedClipRects[i];
+ if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+DisplayItemClip::IntersectWith(const DisplayItemClip& aOther)
+{
+ if (!aOther.mHaveClipRect) {
+ return;
+ }
+ if (!mHaveClipRect) {
+ *this = aOther;
+ return;
+ }
+ if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) {
+ mRoundedClipRects.Clear();
+ return;
+ }
+ mRoundedClipRects.AppendElements(aOther.mRoundedClipRects);
+}
+
+void
+DisplayItemClip::ApplyTo(gfxContext* aContext,
+ nsPresContext* aPresContext,
+ uint32_t aBegin, uint32_t aEnd)
+{
+ int32_t A2D = aPresContext->AppUnitsPerDevPixel();
+ ApplyRectTo(aContext, A2D);
+ ApplyRoundedRectClipsTo(aContext, A2D, aBegin, aEnd);
+}
+
+void
+DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const
+{
+ aContext->NewPath();
+ gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D);
+ aContext->Rectangle(clip, true);
+ aContext->Clip();
+}
+
+void
+DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext,
+ int32_t A2D,
+ uint32_t aBegin, uint32_t aEnd) const
+{
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
+
+ for (uint32_t i = aBegin; i < aEnd; ++i) {
+ RefPtr<Path> roundedRect =
+ MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]);
+ aContext->Clip(roundedRect);
+ }
+}
+
+void
+DisplayItemClip::FillIntersectionOfRoundedRectClips(gfxContext* aContext,
+ const Color& aColor,
+ int32_t aAppUnitsPerDevPixel,
+ uint32_t aBegin,
+ uint32_t aEnd) const
+{
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
+
+ if (aBegin >= aEnd) {
+ return;
+ }
+
+ // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
+ ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, aBegin, aEnd - 1);
+
+ // Now fill the rect at |aEnd - 1|:
+ RefPtr<Path> roundedRect = MakeRoundedRectPath(aDrawTarget,
+ aAppUnitsPerDevPixel,
+ mRoundedClipRects[aEnd - 1]);
+ ColorPattern color(ToDeviceColor(aColor));
+ aDrawTarget.Fill(roundedRect, color);
+
+ // Finally, pop any clips that we may have pushed:
+ for (uint32_t i = aBegin; i < aEnd - 1; ++i) {
+ aContext->PopClip();
+ }
+}
+
+already_AddRefed<Path>
+DisplayItemClip::MakeRoundedRectPath(DrawTarget& aDrawTarget,
+ int32_t A2D,
+ const RoundedRect &aRoundRect) const
+{
+ RectCornerRadii pixelRadii;
+ nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii);
+
+ Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget);
+
+ return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii);
+}
+
+nsRect
+DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const
+{
+ nsRect r = aRect;
+ if (mHaveClipRect) {
+ r.IntersectRect(r, mClipRect);
+ }
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ const RoundedRect &rr = mRoundedClipRects[i];
+ nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r);
+ r = rgn.GetLargestRectangle();
+ }
+ return r;
+}
+
+// Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
+// and radii aXRadius, aYRadius.
+bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
+ nscoord aYRadius, nscoord aYCenter, nscoord aYPoint)
+{
+ float scaledX = float(aXPoint - aXCenter) / float(aXRadius);
+ float scaledY = float(aYPoint - aYCenter) / float(aYRadius);
+ return scaledX * scaledX + scaledY * scaledY < 1.0f;
+}
+
+bool
+DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const
+{
+ if (mRoundedClipRects.IsEmpty())
+ return false;
+
+ nsRect rect;
+ rect.IntersectRect(aRect, NonRoundedIntersection());
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ const RoundedRect &rr = mRoundedClipRects[i];
+ // top left
+ if (rect.x < rr.mRect.x + rr.mRadii[NS_CORNER_TOP_LEFT_X] &&
+ rect.y < rr.mRect.y + rr.mRadii[NS_CORNER_TOP_LEFT_Y]) {
+ if (!IsInsideEllipse(rr.mRadii[NS_CORNER_TOP_LEFT_X],
+ rr.mRect.x + rr.mRadii[NS_CORNER_TOP_LEFT_X],
+ rect.x,
+ rr.mRadii[NS_CORNER_TOP_LEFT_Y],
+ rr.mRect.y + rr.mRadii[NS_CORNER_TOP_LEFT_Y],
+ rect.y)) {
+ return true;
+ }
+ }
+ // top right
+ if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[NS_CORNER_TOP_RIGHT_X] &&
+ rect.y < rr.mRect.y + rr.mRadii[NS_CORNER_TOP_RIGHT_Y]) {
+ if (!IsInsideEllipse(rr.mRadii[NS_CORNER_TOP_RIGHT_X],
+ rr.mRect.XMost() - rr.mRadii[NS_CORNER_TOP_RIGHT_X],
+ rect.XMost(),
+ rr.mRadii[NS_CORNER_TOP_RIGHT_Y],
+ rr.mRect.y + rr.mRadii[NS_CORNER_TOP_RIGHT_Y],
+ rect.y)) {
+ return true;
+ }
+ }
+ // bottom left
+ if (rect.x < rr.mRect.x + rr.mRadii[NS_CORNER_BOTTOM_LEFT_X] &&
+ rect.YMost() > rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y]) {
+ if (!IsInsideEllipse(rr.mRadii[NS_CORNER_BOTTOM_LEFT_X],
+ rr.mRect.x + rr.mRadii[NS_CORNER_BOTTOM_LEFT_X],
+ rect.x,
+ rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y],
+ rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y],
+ rect.YMost())) {
+ return true;
+ }
+ }
+ // bottom right
+ if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X] &&
+ rect.YMost() > rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y]) {
+ if (!IsInsideEllipse(rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X],
+ rr.mRect.XMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X],
+ rect.XMost(),
+ rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y],
+ rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y],
+ rect.YMost())) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsRect
+DisplayItemClip::NonRoundedIntersection() const
+{
+ NS_ASSERTION(mHaveClipRect, "Must have a clip rect!");
+ nsRect result = mClipRect;
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ result.IntersectRect(result, mRoundedClipRects[i].mRect);
+ }
+ return result;
+}
+
+bool
+DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const
+{
+ if (mHaveClipRect && !mClipRect.Contains(aRect)) {
+ return true;
+ }
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ const RoundedRect &rr = mRoundedClipRects[i];
+ nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect);
+ if (!rgn.Contains(aRect)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect,
+ float aXScale,
+ float aYScale,
+ int32_t A2D) const
+{
+ if (mHaveClipRect) {
+ nsIntRect pixelClipRect = mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D);
+ if (!pixelClipRect.Contains(aRect)) {
+ return true;
+ }
+ }
+
+ // Rounded rect clipping only snaps to user-space pixels, not device space.
+ nsIntRect unscaled = aRect;
+ unscaled.Scale(1/aXScale, 1/aYScale);
+
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ const RoundedRect &rr = mRoundedClipRects[i];
+
+ nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D);
+
+ RectCornerRadii pixelRadii;
+ nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii);
+
+ nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect(pixelRect, pixelRadii, unscaled);
+ if (!rgn.Contains(unscaled)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsRect
+DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const
+{
+ if (!mHaveClipRect) {
+ return aRect;
+ }
+
+ nsRect result = aRect.Intersect(mClipRect);
+ for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
+ i < iEnd; ++i) {
+ result = result.Intersect(mRoundedClipRects[i].mRect);
+ }
+ return result;
+}
+
+void
+DisplayItemClip::RemoveRoundedCorners()
+{
+ if (mRoundedClipRects.IsEmpty())
+ return;
+
+ mClipRect = NonRoundedIntersection();
+ mRoundedClipRects.Clear();
+}
+
+// Computes the difference between aR1 and aR2, limited to aBounds.
+static void
+AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, const nsRect& aBounds, nsRegion* aOut)
+{
+ if (aR1.IsEqualInterior(aR2))
+ return;
+ nsRegion r;
+ r.Xor(aR1, aR2);
+ r.And(r, aBounds);
+ aOut->Or(*aOut, r);
+}
+
+void
+DisplayItemClip::AddOffsetAndComputeDifference(uint32_t aStart,
+ const nsPoint& aOffset,
+ const nsRect& aBounds,
+ const DisplayItemClip& aOther,
+ uint32_t aOtherStart,
+ const nsRect& aOtherBounds,
+ nsRegion* aDifference)
+{
+ if (mHaveClipRect != aOther.mHaveClipRect ||
+ aStart != aOtherStart ||
+ mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) {
+ aDifference->Or(*aDifference, aBounds);
+ aDifference->Or(*aDifference, aOtherBounds);
+ return;
+ }
+ if (mHaveClipRect) {
+ AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect,
+ aBounds.Union(aOtherBounds),
+ aDifference);
+ }
+ for (uint32_t i = aStart; i < mRoundedClipRects.Length(); ++i) {
+ if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) {
+ // The corners make it tricky so we'll just add both rects here.
+ aDifference->Or(*aDifference, mRoundedClipRects[i].mRect.Intersect(aBounds));
+ aDifference->Or(*aDifference, aOther.mRoundedClipRects[i].mRect.Intersect(aOtherBounds));
+ }
+ }
+}
+
+uint32_t
+DisplayItemClip::GetCommonRoundedRectCount(const DisplayItemClip& aOther,
+ uint32_t aMax) const
+{
+ uint32_t end = std::min(std::min(mRoundedClipRects.Length(), size_t(aMax)),
+ aOther.mRoundedClipRects.Length());
+ uint32_t clipCount = 0;
+ for (; clipCount < end; ++clipCount) {
+ if (mRoundedClipRects[clipCount] !=
+ aOther.mRoundedClipRects[clipCount]) {
+ return clipCount;
+ }
+ }
+ return clipCount;
+}
+
+void
+DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray, uint32_t aCount) const
+{
+ size_t count = std::min(mRoundedClipRects.Length(), size_t(aCount));
+ aArray->AppendElements(mRoundedClipRects.Elements(), count);
+}
+
+bool
+DisplayItemClip::ComputeRegionInClips(DisplayItemClip* aOldClip,
+ const nsPoint& aShift,
+ nsRegion* aCombined) const
+{
+ if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) {
+ return false;
+ }
+
+ if (aOldClip) {
+ *aCombined = aOldClip->NonRoundedIntersection();
+ aCombined->MoveBy(aShift);
+ aCombined->Or(*aCombined, NonRoundedIntersection());
+ } else {
+ *aCombined = NonRoundedIntersection();
+ }
+ return true;
+}
+
+void
+DisplayItemClip::MoveBy(nsPoint aPoint)
+{
+ if (!mHaveClipRect)
+ return;
+ mClipRect += aPoint;
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ mRoundedClipRects[i].mRect += aPoint;
+ }
+}
+
+static DisplayItemClip* gNoClip;
+
+const DisplayItemClip&
+DisplayItemClip::NoClip()
+{
+ if (!gNoClip) {
+ gNoClip = new DisplayItemClip();
+ }
+ return *gNoClip;
+}
+
+void
+DisplayItemClip::Shutdown()
+{
+ delete gNoClip;
+ gNoClip = nullptr;
+}
+
+nsCString
+DisplayItemClip::ToString() const
+{
+ nsAutoCString str;
+ if (mHaveClipRect) {
+ str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y,
+ mClipRect.width, mClipRect.height);
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ const RoundedRect& r = mRoundedClipRects[i];
+ str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
+ r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height,
+ r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3],
+ r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]);
+ }
+ }
+ return str;
+}
+
+} // namespace mozilla
diff --git a/layout/base/DisplayItemClip.h b/layout/base/DisplayItemClip.h
new file mode 100644
index 000000000..9afca38b8
--- /dev/null
+++ b/layout/base/DisplayItemClip.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYITEMCLIP_H_
+#define DISPLAYITEMCLIP_H_
+
+#include "mozilla/RefPtr.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+#include "nsStyleConsts.h"
+
+class gfxContext;
+class nsPresContext;
+class nsRegion;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class Path;
+} // namespace gfx
+} // namespace mozilla
+
+namespace mozilla {
+
+/**
+ * An DisplayItemClip represents the intersection of an optional rectangle
+ * with a list of rounded rectangles (which is often empty), all in appunits.
+ * It can represent everything CSS clipping can do to an element (except for
+ * SVG clip-path), including no clipping at all.
+ */
+class DisplayItemClip {
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Path Path;
+
+public:
+ struct RoundedRect {
+ nsRect mRect;
+ // Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
+ nscoord mRadii[8];
+
+ RoundedRect operator+(const nsPoint& aOffset) const {
+ RoundedRect r = *this;
+ r.mRect += aOffset;
+ return r;
+ }
+ bool operator==(const RoundedRect& aOther) const {
+ if (!mRect.IsEqualInterior(aOther.mRect)) {
+ return false;
+ }
+
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ if (mRadii[corner] != aOther.mRadii[corner]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!=(const RoundedRect& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+
+ // Constructs a DisplayItemClip that does no clipping at all.
+ DisplayItemClip() : mHaveClipRect(false) {}
+
+ void SetTo(const nsRect& aRect);
+ void SetTo(const nsRect& aRect, const nscoord* aRadii);
+ void SetTo(const nsRect& aRect, const nsRect& aRoundedRect, const nscoord* aRadii);
+ void IntersectWith(const DisplayItemClip& aOther);
+
+ // Apply this |DisplayItemClip| to the given gfxContext. Any saving of state
+ // or clearing of other clips must be done by the caller.
+ // See aBegin/aEnd note on ApplyRoundedRectsTo.
+ void ApplyTo(gfxContext* aContext, nsPresContext* aPresContext,
+ uint32_t aBegin = 0, uint32_t aEnd = UINT32_MAX);
+
+ void ApplyRectTo(gfxContext* aContext, int32_t A2D) const;
+ // Applies the rounded rects in this Clip to aContext
+ // Will only apply rounded rects from aBegin (inclusive) to aEnd
+ // (exclusive) or the number of rounded rects, whichever is smaller.
+ void ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2DPRInt32,
+ uint32_t aBegin, uint32_t aEnd) const;
+
+ // Draw (fill) the rounded rects in this clip to aContext
+ void FillIntersectionOfRoundedRectClips(gfxContext* aContext,
+ const Color& aColor,
+ int32_t aAppUnitsPerDevPixel,
+ uint32_t aBegin,
+ uint32_t aEnd) const;
+ // 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext
+ already_AddRefed<Path> MakeRoundedRectPath(DrawTarget& aDrawTarget,
+ int32_t A2D,
+ const RoundedRect &aRoundRect) const;
+
+ // Returns true if the intersection of aRect and this clip region is
+ // non-empty. This is precise for DisplayItemClips with at most one
+ // rounded rectangle. When multiple rounded rectangles are present, we just
+ // check that the rectangle intersects all of them (but possibly in different
+ // places). So it may return true when the correct answer is false.
+ bool MayIntersect(const nsRect& aRect) const;
+
+ // Return a rectangle contained in the intersection of aRect with this
+ // clip region. Tries to return the largest possible rectangle, but may
+ // not succeed.
+ nsRect ApproximateIntersectInward(const nsRect& aRect) const;
+
+ /*
+ * Computes a region which contains the clipped area of this DisplayItemClip,
+ * or if aOldClip is non-null, the union of the clipped area of this
+ * DisplayItemClip with the clipped area of aOldClip translated by aShift.
+ * The result is stored in aCombined. If the result would be infinite
+ * (because one or both of the clips does no clipping), returns false.
+ */
+ bool ComputeRegionInClips(DisplayItemClip* aOldClip,
+ const nsPoint& aShift,
+ nsRegion* aCombined) const;
+
+ // Returns false if aRect is definitely not clipped by a rounded corner in
+ // this clip. Returns true if aRect is clipped by a rounded corner in this
+ // clip or it can not be quickly determined that it is not clipped by a
+ // rounded corner in this clip.
+ bool IsRectClippedByRoundedCorner(const nsRect& aRect) const;
+
+ // Returns false if aRect is definitely not clipped by anything in this clip.
+ // Fast but not necessarily accurate.
+ bool IsRectAffectedByClip(const nsRect& aRect) const;
+ bool IsRectAffectedByClip(const nsIntRect& aRect, float aXScale, float aYScale, int32_t A2D) const;
+
+ // Intersection of all rects in this clip ignoring any rounded corners.
+ nsRect NonRoundedIntersection() const;
+
+ // Intersect the given rects with all rects in this clip, ignoring any
+ // rounded corners.
+ nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const;
+
+ // Gets rid of any rounded corners in this clip.
+ void RemoveRoundedCorners();
+
+ // Adds the difference between Intersect(*this + aPoint, aBounds) and
+ // Intersect(aOther, aOtherBounds) to aDifference (or a bounding-box thereof).
+ void AddOffsetAndComputeDifference(uint32_t aStart, const nsPoint& aPoint, const nsRect& aBounds,
+ const DisplayItemClip& aOther, uint32_t aOtherStart, const nsRect& aOtherBounds,
+ nsRegion* aDifference);
+
+ bool operator==(const DisplayItemClip& aOther) const {
+ return mHaveClipRect == aOther.mHaveClipRect &&
+ (!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) &&
+ mRoundedClipRects == aOther.mRoundedClipRects;
+ }
+ bool operator!=(const DisplayItemClip& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool HasClip() const { return mHaveClipRect; }
+ const nsRect& GetClipRect() const
+ {
+ NS_ASSERTION(HasClip(), "No clip rect!");
+ return mClipRect;
+ }
+
+ void MoveBy(nsPoint aPoint);
+
+ nsCString ToString() const;
+
+ /**
+ * Find the largest N such that the first N rounded rects in 'this' are
+ * equal to the first N rounded rects in aOther, and N <= aMax.
+ */
+ uint32_t GetCommonRoundedRectCount(const DisplayItemClip& aOther,
+ uint32_t aMax) const;
+ uint32_t GetRoundedRectCount() const { return mRoundedClipRects.Length(); }
+ void AppendRoundedRects(nsTArray<RoundedRect>* aArray, uint32_t aCount) const;
+
+ static const DisplayItemClip& NoClip();
+
+ static void Shutdown();
+
+private:
+ nsRect mClipRect;
+ nsTArray<RoundedRect> mRoundedClipRects;
+ // If mHaveClipRect is false then this object represents no clipping at all
+ // and mRoundedClipRects must be empty.
+ bool mHaveClipRect;
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYITEMCLIP_H_ */
diff --git a/layout/base/DisplayItemScrollClip.cpp b/layout/base/DisplayItemScrollClip.cpp
new file mode 100644
index 000000000..a78a43dbd
--- /dev/null
+++ b/layout/base/DisplayItemScrollClip.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayItemScrollClip.h"
+
+#include "DisplayItemClip.h"
+
+namespace mozilla {
+
+/* static */ bool
+DisplayItemScrollClip::IsAncestor(const DisplayItemScrollClip* aAncestor,
+ const DisplayItemScrollClip* aDescendant)
+{
+ if (!aAncestor) {
+ // null means root.
+ return true;
+ }
+
+ for (const DisplayItemScrollClip* sc = aDescendant; sc; sc = sc->mParent) {
+ if (sc == aAncestor) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+DisplayItemScrollClip::HasRoundedCorners() const
+{
+ for (const DisplayItemScrollClip* scrollClip = this;
+ scrollClip; scrollClip = scrollClip->mParent) {
+ if (scrollClip->mClip->GetRoundedRectCount() > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ nsCString
+DisplayItemScrollClip::ToString(const DisplayItemScrollClip* aScrollClip)
+{
+ nsAutoCString str;
+ for (const DisplayItemScrollClip* scrollClip = aScrollClip;
+ scrollClip; scrollClip = scrollClip->mParent) {
+ str.AppendPrintf("<%s>%s", scrollClip->mClip ? scrollClip->mClip->ToString().get() : "null",
+ scrollClip->mIsAsyncScrollable ? " [async-scrollable]" : "");
+ if (scrollClip->mParent) {
+ str.Append(", ");
+ }
+ }
+ return str;
+}
+
+} // namespace mozilla
diff --git a/layout/base/DisplayItemScrollClip.h b/layout/base/DisplayItemScrollClip.h
new file mode 100644
index 000000000..ee3335972
--- /dev/null
+++ b/layout/base/DisplayItemScrollClip.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYITEMSCROLLCLIP_H_
+#define DISPLAYITEMSCROLLCLIP_H_
+
+#include "mozilla/Assertions.h"
+#include "nsString.h"
+
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+class DisplayItemClip;
+
+/**
+ * A DisplayItemScrollClip represents a DisplayItemClip from a scrollable
+ * frame. This clip is stored on a display item separately from the item's
+ * regular DisplayItemClip for async scrolling purposes: The item's regular
+ * clip can be fused to the item's contents when drawing layer contents, but
+ * scroll clips need to be kept separate so that they can be applied at
+ * composition time, after any async scroll offsets have been applied.
+ * Scroll clips are created during display list construction when
+ * async-scrollable scroll frames are entered. At that point, the current
+ * regular clip is cleared on the display list clip state. They are also
+ * created for scroll frames that are inactivate and not async scrollable;
+ * in that case, mIsAsyncScrollable on the scroll clip will be false.
+ * When async-scrollable scroll frames are nested, the inner display items
+ * can walk the whole chain of scroll clips via the DisplayItemScrollClip's
+ * mParent pointer.
+ * Storing scroll clips on display items allows easy access of all scroll
+ * frames that affect a certain display item, and it allows some display list
+ * operations to compute more accurate clips, for example when computing the
+ * bounds of a container item for a frame that contains an async-scrollable
+ * scroll frame.
+ */
+class DisplayItemScrollClip {
+public:
+ DisplayItemScrollClip(const DisplayItemScrollClip* aParent,
+ nsIScrollableFrame* aScrollableFrame,
+ const DisplayItemClip* aClip,
+ bool aIsAsyncScrollable)
+ : mParent(aParent)
+ , mScrollableFrame(aScrollableFrame)
+ , mClip(aClip)
+ , mIsAsyncScrollable(aIsAsyncScrollable)
+ , mDepth(aParent ? aParent->mDepth + 1 : 1)
+ {
+ MOZ_ASSERT(mScrollableFrame);
+ }
+
+ /**
+ * "Pick descendant" is analogous to the intersection operation on regular
+ * clips: In some cases, there are multiple candidate clips that can apply to
+ * an item, one of them being the ancestor of the other. This method picks
+ * the descendant.
+ * Both aClip1 and aClip2 are allowed to be null.
+ */
+ static const DisplayItemScrollClip*
+ PickDescendant(const DisplayItemScrollClip* aClip1,
+ const DisplayItemScrollClip* aClip2)
+ {
+ MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1),
+ "one of the scroll clips must be an ancestor of the other");
+ return Depth(aClip1) > Depth(aClip2) ? aClip1 : aClip2;
+ }
+
+ static const DisplayItemScrollClip*
+ PickAncestor(const DisplayItemScrollClip* aClip1,
+ const DisplayItemScrollClip* aClip2)
+ {
+ MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1),
+ "one of the scroll clips must be an ancestor of the other");
+ return Depth(aClip1) < Depth(aClip2) ? aClip1 : aClip2;
+ }
+
+
+ /**
+ * Returns whether aAncestor is an ancestor scroll clip of aDescendant.
+ * A scroll clip that's null is considered the root scroll clip.
+ */
+ static bool IsAncestor(const DisplayItemScrollClip* aAncestor,
+ const DisplayItemScrollClip* aDescendant);
+
+ /**
+ * Return a string which contains the list of stringified clips for this
+ * scroll clip and its ancestors. aScrollClip can be null.
+ */
+ static nsCString ToString(const DisplayItemScrollClip* aScrollClip);
+
+ bool HasRoundedCorners() const;
+
+ /**
+ * The previous (outer) scroll clip, or null.
+ */
+ const DisplayItemScrollClip* mParent;
+
+ /**
+ * The scrollable frame that this scroll clip is for. Always non-null.
+ */
+ nsIScrollableFrame* mScrollableFrame;
+
+ /**
+ * The clip represented by this scroll clip, relative to mScrollableFrame's
+ * reference frame. Can be null.
+ */
+ const DisplayItemClip* mClip;
+
+ /**
+ * Whether this scroll clip is for an async-scrollable scroll frame.
+ * Can change during display list construction.
+ */
+ bool mIsAsyncScrollable;
+
+private:
+ static uint32_t Depth(const DisplayItemScrollClip* aSC)
+ { return aSC ? aSC->mDepth : 0; }
+
+ const uint32_t mDepth;
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYITEMSCROLLCLIP_H_ */
diff --git a/layout/base/DisplayListClipState.cpp b/layout/base/DisplayListClipState.cpp
new file mode 100644
index 000000000..5ec065b95
--- /dev/null
+++ b/layout/base/DisplayListClipState.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DisplayListClipState.h"
+
+#include "DisplayItemScrollClip.h"
+#include "nsDisplayList.h"
+
+namespace mozilla {
+
+const DisplayItemClip*
+DisplayListClipState::GetCurrentCombinedClip(nsDisplayListBuilder* aBuilder)
+{
+ if (mCurrentCombinedClip) {
+ return mCurrentCombinedClip;
+ }
+ if (!mClipContentDescendants && !mClipContainingBlockDescendants) {
+ return nullptr;
+ }
+ if (mClipContentDescendants) {
+ if (mClipContainingBlockDescendants) {
+ DisplayItemClip intersection = *mClipContentDescendants;
+ intersection.IntersectWith(*mClipContainingBlockDescendants);
+ mCurrentCombinedClip = aBuilder->AllocateDisplayItemClip(intersection);
+ } else {
+ mCurrentCombinedClip =
+ aBuilder->AllocateDisplayItemClip(*mClipContentDescendants);
+ }
+ } else {
+ mCurrentCombinedClip =
+ aBuilder->AllocateDisplayItemClip(*mClipContainingBlockDescendants);
+ }
+ return mCurrentCombinedClip;
+}
+
+void
+DisplayListClipState::SetScrollClipForContainingBlockDescendants(
+ nsDisplayListBuilder* aBuilder,
+ const DisplayItemScrollClip* aScrollClip)
+{
+ if (aBuilder->IsPaintingToWindow() &&
+ mClipContentDescendants &&
+ aScrollClip != mScrollClipContainingBlockDescendants) {
+ // Disable paint skipping for all scroll frames on the way to aScrollClip.
+ for (const DisplayItemScrollClip* sc = mClipContentDescendantsScrollClip;
+ sc && !DisplayItemScrollClip::IsAncestor(sc, aScrollClip);
+ sc = sc->mParent) {
+ if (sc->mScrollableFrame) {
+ sc->mScrollableFrame->SetScrollsClipOnUnscrolledOutOfFlow();
+ }
+ }
+ mClipContentDescendantsScrollClip = nullptr;
+ }
+ mScrollClipContainingBlockDescendants = aScrollClip;
+ mStackingContextAncestorSC = DisplayItemScrollClip::PickAncestor(mStackingContextAncestorSC, aScrollClip);
+}
+
+void
+DisplayListClipState::ClipContainingBlockDescendants(const nsRect& aRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack)
+{
+ if (aRadii) {
+ aClipOnStack.SetTo(aRect, aRadii);
+ } else {
+ aClipOnStack.SetTo(aRect);
+ }
+ if (mClipContainingBlockDescendants) {
+ aClipOnStack.IntersectWith(*mClipContainingBlockDescendants);
+ }
+ mClipContainingBlockDescendants = &aClipOnStack;
+ mClipContentDescendantsScrollClip = GetCurrentInnermostScrollClip();
+ mCurrentCombinedClip = nullptr;
+}
+
+void
+DisplayListClipState::ClipContentDescendants(const nsRect& aRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack)
+{
+ if (aRadii) {
+ aClipOnStack.SetTo(aRect, aRadii);
+ } else {
+ aClipOnStack.SetTo(aRect);
+ }
+ if (mClipContentDescendants) {
+ aClipOnStack.IntersectWith(*mClipContentDescendants);
+ }
+ mClipContentDescendants = &aClipOnStack;
+ mCurrentCombinedClip = nullptr;
+}
+
+void
+DisplayListClipState::ClipContentDescendants(const nsRect& aRect,
+ const nsRect& aRoundedRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack)
+{
+ if (aRadii) {
+ aClipOnStack.SetTo(aRect, aRoundedRect, aRadii);
+ } else {
+ nsRect intersect = aRect.Intersect(aRoundedRect);
+ aClipOnStack.SetTo(intersect);
+ }
+ if (mClipContentDescendants) {
+ aClipOnStack.IntersectWith(*mClipContentDescendants);
+ }
+ mClipContentDescendants = &aClipOnStack;
+ mCurrentCombinedClip = nullptr;
+}
+
+void
+DisplayListClipState::ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ DisplayItemClip& aClipOnStack,
+ uint32_t aFlags)
+{
+ nscoord radii[8];
+ bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii);
+ if (!hasBorderRadius && (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) {
+ return;
+ }
+
+ nsRect clipRect = aFrame->GetContentRectRelativeToSelf() +
+ aBuilder->ToReferenceFrame(aFrame);
+ // If we have a border-radius, we have to clip our content to that
+ // radius.
+ ClipContainingBlockDescendants(clipRect, hasBorderRadius ? radii : nullptr,
+ aClipOnStack);
+}
+
+const DisplayItemScrollClip*
+DisplayListClipState::GetCurrentInnermostScrollClip()
+{
+ return DisplayItemScrollClip::PickDescendant(
+ mScrollClipContentDescendants, mScrollClipContainingBlockDescendants);
+}
+
+void
+DisplayListClipState::TurnClipIntoScrollClipForContentDescendants(
+ nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+{
+ const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
+ mScrollClipContentDescendants =
+ aBuilder->AllocateDisplayItemScrollClip(parent,
+ aScrollableFrame,
+ GetCurrentCombinedClip(aBuilder), true);
+ Clear();
+}
+
+void
+DisplayListClipState::TurnClipIntoScrollClipForContainingBlockDescendants(
+ nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+{
+ const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
+ mScrollClipContainingBlockDescendants =
+ aBuilder->AllocateDisplayItemScrollClip(parent,
+ aScrollableFrame,
+ GetCurrentCombinedClip(aBuilder), true);
+ Clear();
+}
+
+const DisplayItemClip*
+WithoutRoundedCorners(nsDisplayListBuilder* aBuilder, const DisplayItemClip* aClip)
+{
+ if (!aClip) {
+ return nullptr;
+ }
+ DisplayItemClip rectClip(*aClip);
+ rectClip.RemoveRoundedCorners();
+ return aBuilder->AllocateDisplayItemClip(rectClip);
+}
+
+DisplayItemScrollClip*
+DisplayListClipState::CreateInactiveScrollClip(
+ nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+{
+ // We ignore the rounded corners on the current clip because we don't want
+ // them to be double-applied (as scroll clip and as regular clip).
+ // Double-applying rectangle clips doesn't make a visual difference so it's
+ // fine.
+ const DisplayItemClip* rectClip =
+ WithoutRoundedCorners(aBuilder, GetCurrentCombinedClip(aBuilder));
+
+ const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
+ DisplayItemScrollClip* scrollClip =
+ aBuilder->AllocateDisplayItemScrollClip(parent,
+ aScrollableFrame,
+ rectClip, false);
+ return scrollClip;
+}
+
+DisplayItemScrollClip*
+DisplayListClipState::InsertInactiveScrollClipForContentDescendants(
+ nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+{
+ DisplayItemScrollClip* scrollClip =
+ CreateInactiveScrollClip(aBuilder, aScrollableFrame);
+ mScrollClipContentDescendants = scrollClip;
+ return scrollClip;
+}
+
+DisplayItemScrollClip*
+DisplayListClipState::InsertInactiveScrollClipForContainingBlockDescendants(
+ nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+{
+ DisplayItemScrollClip* scrollClip =
+ CreateInactiveScrollClip(aBuilder, aScrollableFrame);
+ mScrollClipContainingBlockDescendants = scrollClip;
+ return scrollClip;
+}
+
+DisplayListClipState::AutoSaveRestore::AutoSaveRestore(nsDisplayListBuilder* aBuilder)
+ : mState(aBuilder->ClipState())
+ , mSavedState(aBuilder->ClipState())
+#ifdef DEBUG
+ , mClipUsed(false)
+ , mRestored(false)
+#endif
+ , mClearedForStackingContextContents(false)
+{
+ mState.mStackingContextAncestorSC = mState.GetCurrentInnermostScrollClip();
+}
+
+} // namespace mozilla
diff --git a/layout/base/DisplayListClipState.h b/layout/base/DisplayListClipState.h
new file mode 100644
index 000000000..6576ef410
--- /dev/null
+++ b/layout/base/DisplayListClipState.h
@@ -0,0 +1,468 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DISPLAYLISTCLIPSTATE_H_
+#define DISPLAYLISTCLIPSTATE_H_
+
+#include "DisplayItemClip.h"
+#include "DisplayItemScrollClip.h"
+
+#include "mozilla/DebugOnly.h"
+
+class nsIFrame;
+class nsIScrollableFrame;
+class nsDisplayListBuilder;
+
+namespace mozilla {
+
+/**
+ * All clip coordinates are in appunits relative to the reference frame
+ * for the display item we're building.
+ */
+class DisplayListClipState {
+public:
+ DisplayListClipState()
+ : mClipContentDescendants(nullptr)
+ , mClipContainingBlockDescendants(nullptr)
+ , mCurrentCombinedClip(nullptr)
+ , mScrollClipContentDescendants(nullptr)
+ , mScrollClipContainingBlockDescendants(nullptr)
+ , mClipContentDescendantsScrollClip(nullptr)
+ , mStackingContextAncestorSC(nullptr)
+ {}
+
+ /**
+ * Returns intersection of mClipContainingBlockDescendants and
+ * mClipContentDescendants, allocated on aBuilder's arena.
+ */
+ const DisplayItemClip* GetCurrentCombinedClip(nsDisplayListBuilder* aBuilder);
+
+ const DisplayItemClip* GetClipForContainingBlockDescendants() const
+ {
+ return mClipContainingBlockDescendants;
+ }
+ const DisplayItemClip* GetClipForContentDescendants() const
+ {
+ return mClipContentDescendants;
+ }
+
+ const DisplayItemScrollClip* GetCurrentInnermostScrollClip();
+
+ const DisplayItemScrollClip* CurrentAncestorScrollClipForStackingContextContents()
+ {
+ return mStackingContextAncestorSC;
+ }
+
+ class AutoSaveRestore;
+ friend class AutoSaveRestore;
+
+ class AutoClipContainingBlockDescendantsToContentBox;
+ friend class AutoClipContainingBlockDescendantsToContentBox;
+
+ class AutoClipMultiple;
+ friend class AutoClipMultiple;
+
+ enum {
+ ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01
+ };
+
+private:
+ void SetClipForContainingBlockDescendants(const DisplayItemClip* aClip)
+ {
+ mClipContainingBlockDescendants = aClip;
+ mCurrentCombinedClip = nullptr;
+ }
+
+ void SetScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
+ const DisplayItemScrollClip* aScrollClip);
+
+ void Clear()
+ {
+ mClipContentDescendants = nullptr;
+ mClipContainingBlockDescendants = nullptr;
+ mCurrentCombinedClip = nullptr;
+ // We do not clear scroll clips.
+ }
+
+ void EnterStackingContextContents(bool aClear)
+ {
+ if (aClear) {
+ mClipContentDescendants = nullptr;
+ mClipContainingBlockDescendants = nullptr;
+ mCurrentCombinedClip = nullptr;
+ mScrollClipContentDescendants = nullptr;
+ mScrollClipContainingBlockDescendants = nullptr;
+ mStackingContextAncestorSC = nullptr;
+ } else {
+ mStackingContextAncestorSC = GetCurrentInnermostScrollClip();
+ }
+ }
+
+ /**
+ * Clear the current clip, and instead add it as a scroll clip to the current
+ * scroll clip chain.
+ */
+ void TurnClipIntoScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder,
+ nsIScrollableFrame* aScrollableFrame);
+ void TurnClipIntoScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
+ nsIScrollableFrame* aScrollableFrame);
+
+ /**
+ * Insert a scroll clip without clearing the current clip.
+ * The returned DisplayItemScrollClip will have mIsAsyncScrollable == false,
+ * and it can be activated once the scroll frame knows that it needs to be
+ * async scrollable.
+ */
+ DisplayItemScrollClip* InsertInactiveScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder,
+ nsIScrollableFrame* aScrollableFrame);
+ DisplayItemScrollClip* InsertInactiveScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
+ nsIScrollableFrame* aScrollableFrame);
+
+ DisplayItemScrollClip* CreateInactiveScrollClip(nsDisplayListBuilder* aBuilder,
+ nsIScrollableFrame* aScrollableFrame);
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendants(const nsRect& aRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack);
+
+ void ClipContentDescendants(const nsRect& aRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack);
+ void ClipContentDescendants(const nsRect& aRect,
+ const nsRect& aRoundedRect,
+ const nscoord* aRadii,
+ DisplayItemClip& aClipOnStack);
+
+ /**
+ * Clips containing-block descendants to the frame's content-box,
+ * taking border-radius into account.
+ * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
+ * we assume display items will not draw outside the content rect, so
+ * clipping is only required if there is a border-radius. This is an
+ * optimization to reduce the amount of clipping required.
+ */
+ void ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ DisplayItemClip& aClipOnStack,
+ uint32_t aFlags);
+
+ /**
+ * All content descendants (i.e. following placeholder frames to their
+ * out-of-flows if necessary) should be clipped by mClipContentDescendants.
+ * Null if no clipping applies.
+ */
+ const DisplayItemClip* mClipContentDescendants;
+ /**
+ * All containing-block descendants (i.e. frame descendants), including
+ * display items for the current frame, should be clipped by
+ * mClipContainingBlockDescendants.
+ * Null if no clipping applies.
+ */
+ const DisplayItemClip* mClipContainingBlockDescendants;
+ /**
+ * The intersection of mClipContentDescendants and
+ * mClipContainingBlockDescendants.
+ * Allocated in the nsDisplayListBuilder arena. Null if none has been
+ * allocated or both mClipContentDescendants and mClipContainingBlockDescendants
+ * are null.
+ */
+ const DisplayItemClip* mCurrentCombinedClip;
+
+ /**
+ * The same for scroll clips.
+ */
+ const DisplayItemScrollClip* mScrollClipContentDescendants;
+ const DisplayItemScrollClip* mScrollClipContainingBlockDescendants;
+
+ /**
+ * The scroll clip that was in effect when mClipContentDescendants was set.
+ */
+ const DisplayItemScrollClip* mClipContentDescendantsScrollClip;
+
+ /**
+ * A scroll clip that is an ancestor of all the scroll clips that were
+ * "current" on this clip state since EnterStackingContextContents was
+ * called.
+ */
+ const DisplayItemScrollClip* mStackingContextAncestorSC;
+};
+
+/**
+ * A class to automatically save and restore the current clip state. Also
+ * offers methods for modifying the clip state. Only one modification is allowed
+ * to be in scope at a time using one of these objects; multiple modifications
+ * require nested objects. The interface is written this way to prevent
+ * dangling pointers to DisplayItemClips.
+ */
+class DisplayListClipState::AutoSaveRestore {
+public:
+ explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder);
+ void Restore()
+ {
+ if (!mClearedForStackingContextContents) {
+ // Forward along the ancestor scroll clip to the original clip state.
+ mSavedState.mStackingContextAncestorSC =
+ DisplayItemScrollClip::PickAncestor(mSavedState.mStackingContextAncestorSC,
+ mState.mStackingContextAncestorSC);
+ }
+ mState = mSavedState;
+#ifdef DEBUG
+ mRestored = true;
+#endif
+ }
+ ~AutoSaveRestore()
+ {
+ Restore();
+ }
+
+ void Clear()
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ mState.Clear();
+#ifdef DEBUG
+ mClipUsed = false;
+#endif
+ }
+
+ void EnterStackingContextContents(bool aClear)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ mState.EnterStackingContextContents(aClear);
+ mClearedForStackingContextContents = aClear;
+ }
+
+ void ExitStackingContextContents(const DisplayItemScrollClip** aOutContainerSC)
+ {
+ if (mClearedForStackingContextContents) {
+ // If we cleared the scroll clip, then the scroll clip that was current
+ // just before we cleared it is the one that needs to be set on the
+ // container item.
+ *aOutContainerSC = mSavedState.GetCurrentInnermostScrollClip();
+ } else {
+ // If we didn't clear the scroll clip, then the container item needs to
+ // get a scroll clip that's an ancestor of all its direct child items'
+ // scroll clips.
+ // The simplest way to satisfy this requirement would be to just take the
+ // root scroll clip (i.e. nullptr). However, this can cause the bounds of
+ // the container items to be enlarged unnecessarily, so instead we try to
+ // take the "deepest" scroll clip that satisfies the requirement.
+ // Usually this is the scroll clip that was current before we entered
+ // the stacking context contents (call that the "initial scroll clip").
+ // There are two cases in which the container scroll clip *won't* be the
+ // initial scroll clip (instead the container scroll clip will be a
+ // proper ancestor of the initial scroll clip):
+ // (1) If SetScrollClipForContainingBlockDescendants was called with an
+ // ancestor scroll clip of the initial scroll clip while we were
+ // building our direct child items. This happens if we entered a
+ // position:absolute or position:fixed element whose containing
+ // block is an ancestor of the frame that generated the initial
+ // scroll clip. Then the "ancestor scroll clip for stacking context
+ // contents" will be set to that scroll clip.
+ // (2) If one of our direct child items is a container item for which
+ // (1) or (2) happened.
+ *aOutContainerSC = mState.CurrentAncestorScrollClipForStackingContextContents();
+ }
+ Restore();
+ }
+
+ bool SavedStateHasRoundedCorners()
+ {
+ const DisplayItemScrollClip* scrollClip = mSavedState.GetCurrentInnermostScrollClip();
+ if (scrollClip && scrollClip->HasRoundedCorners()) {
+ return true;
+ }
+ const DisplayItemClip* clip = mSavedState.GetClipForContainingBlockDescendants();
+ if (clip && clip->GetRoundedRectCount() > 0) {
+ return true;
+ }
+
+ clip = mSavedState.GetClipForContentDescendants();
+ if (clip && clip->GetRoundedRectCount() > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ void TurnClipIntoScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ mState.TurnClipIntoScrollClipForContentDescendants(aBuilder, aScrollableFrame);
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ }
+
+ void TurnClipIntoScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ mState.TurnClipIntoScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame);
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ }
+
+ DisplayItemScrollClip* InsertInactiveScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContentDescendants(aBuilder, aScrollableFrame);
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ return scrollClip;
+ }
+
+ DisplayItemScrollClip* InsertInactiveScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame);
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ return scrollClip;
+ }
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendants(const nsRect& aRect,
+ const nscoord* aRadii = nullptr)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendants(aRect, aRadii, mClip);
+ }
+
+ void ClipContentDescendants(const nsRect& aRect,
+ const nscoord* aRadii = nullptr)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContentDescendants(aRect, aRadii, mClip);
+ }
+
+ void ClipContentDescendants(const nsRect& aRect,
+ const nsRect& aRoundedRect,
+ const nscoord* aRadii = nullptr)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContentDescendants(aRect, aRoundedRect, aRadii, mClip);
+ }
+
+ /**
+ * Clips containing-block descendants to the frame's content-box,
+ * taking border-radius into account.
+ * If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
+ * we assume display items will not draw outside the content rect, so
+ * clipping is only required if there is a border-radius. This is an
+ * optimization to reduce the amount of clipping required.
+ */
+ void ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ uint32_t aFlags = 0)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mClipUsed, "mClip already used");
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags);
+ }
+
+protected:
+ DisplayListClipState& mState;
+ DisplayListClipState mSavedState;
+ DisplayItemClip mClip;
+#ifdef DEBUG
+ bool mClipUsed;
+ bool mRestored;
+#endif
+ bool mClearedForStackingContextContents;
+};
+
+class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox : public AutoSaveRestore {
+public:
+ AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ uint32_t aFlags = 0)
+ : AutoSaveRestore(aBuilder)
+ {
+#ifdef DEBUG
+ mClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags);
+ }
+};
+
+/**
+ * Do not use this outside of nsFrame::BuildDisplayListForChild, use
+ * multiple AutoSaveRestores instead. We provide this class just to ensure
+ * BuildDisplayListForChild is as efficient as possible.
+ */
+class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore {
+public:
+ explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder)
+ : AutoSaveRestore(aBuilder)
+#ifdef DEBUG
+ , mExtraClipUsed(false)
+#endif
+ {}
+
+ /**
+ * *aClip must survive longer than this object. Be careful!!!
+ */
+ void SetClipForContainingBlockDescendants(const DisplayItemClip* aClip)
+ {
+ mState.SetClipForContainingBlockDescendants(aClip);
+ }
+
+ void SetScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
+ const DisplayItemScrollClip* aScrollClip)
+ {
+ mState.SetScrollClipForContainingBlockDescendants(aBuilder, aScrollClip);
+ }
+
+ /**
+ * Intersects the given clip rect (with optional aRadii) with the current
+ * mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
+ * the result, stored in aClipOnStack.
+ */
+ void ClipContainingBlockDescendantsExtra(const nsRect& aRect,
+ const nscoord* aRadii)
+ {
+ NS_ASSERTION(!mRestored, "Already restored!");
+ NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used");
+#ifdef DEBUG
+ mExtraClipUsed = true;
+#endif
+ mState.ClipContainingBlockDescendants(aRect, aRadii, mExtraClip);
+ }
+
+protected:
+ DisplayItemClip mExtraClip;
+#ifdef DEBUG
+ bool mExtraClipUsed;
+#endif
+};
+
+} // namespace mozilla
+
+#endif /* DISPLAYLISTCLIPSTATE_H_ */
diff --git a/layout/base/DottedCornerFinder.cpp b/layout/base/DottedCornerFinder.cpp
new file mode 100644
index 000000000..fb9534f54
--- /dev/null
+++ b/layout/base/DottedCornerFinder.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DottedCornerFinder.h"
+
+#include "mozilla/Move.h"
+#include "BorderCache.h"
+#include "BorderConsts.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+static inline Float
+Square(Float x)
+{
+ return x * x;
+}
+
+static Point
+PointRotateCCW90(const Point& aP)
+{
+ return Point(aP.y, -aP.x);
+}
+
+struct BestOverlap
+{
+ Float overlap;
+ size_t count;
+
+ BestOverlap()
+ : overlap(0.0f), count(0)
+ {}
+
+ BestOverlap(Float aOverlap, size_t aCount)
+ : overlap(aOverlap), count(aCount)
+ {}
+};
+
+static const size_t DottedCornerCacheSize = 256;
+nsDataHashtable<FourFloatsHashKey, BestOverlap> DottedCornerCache;
+
+DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier,
+ const Bezier& aInnerBezier,
+ mozilla::css::Corner aCorner,
+ Float aBorderRadiusX,
+ Float aBorderRadiusY,
+ const Point& aC0, Float aR0,
+ const Point& aCn, Float aRn,
+ const Size& aCornerDim)
+ : mOuterBezier(aOuterBezier),
+ mInnerBezier(aInnerBezier),
+ mCorner(aCorner),
+ mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f),
+ mC0(aC0), mCn(aCn),
+ mR0(aR0), mRn(aRn), mMaxR(std::max(aR0, aRn)),
+ mCenterCurveOrigin(mC0.x, mCn.y),
+ mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y),
+ mBestOverlap(0.0f),
+ mHasZeroBorderWidth(false), mHasMore(true),
+ mMaxCount(aCornerDim.width + aCornerDim.height),
+ mType(OTHER),
+ mI(0), mCount(0)
+{
+ NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f,
+ "At least one side should have non-zero radius.");
+
+ mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
+ mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
+
+ DetermineType(aBorderRadiusX, aBorderRadiusY);
+
+ Reset();
+}
+
+static bool
+IsSingleCurve(Float aMinR, Float aMaxR,
+ Float aMinBorderRadius, Float aMaxBorderRadius)
+{
+ return aMinR > 0.0f &&
+ aMinBorderRadius > aMaxR * 4.0f &&
+ aMinBorderRadius / aMaxBorderRadius > 0.5f;
+}
+
+void
+DottedCornerFinder::DetermineType(Float aBorderRadiusX, Float aBorderRadiusY)
+{
+ // Calculate parameters for the center curve before swap.
+ Float centerCurveWidth = fabs(mC0.x - mCn.x);
+ Float centerCurveHeight = fabs(mC0.y - mCn.y);
+ Point cornerPoint(mCn.x, mC0.y);
+
+ bool swapped = false;
+ if (mR0 < mRn) {
+ // Always draw from wider side to thinner side.
+ Swap(mC0, mCn);
+ Swap(mR0, mRn);
+ Swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
+ Swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
+ Swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
+ Swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
+ mNormalSign = -mNormalSign;
+ swapped = true;
+ }
+
+ // See the comment at mType declaration for each condition.
+
+ Float minR = std::min(mR0, mRn);
+ Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY);
+ Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY);
+ if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) {
+ if (mR0 == mRn) {
+ Float borderLength;
+ if (minBorderRadius == maxBorderRadius) {
+ mType = PERFECT;
+ borderLength = M_PI * centerCurveHeight / 2.0f;
+
+ mCenterCurveR = centerCurveWidth;
+ } else {
+ mType = SINGLE_CURVE_AND_RADIUS;
+ borderLength = GetQuarterEllipticArcLength(centerCurveWidth,
+ centerCurveHeight);
+ }
+
+ Float diameter = mR0 * 2.0f;
+ size_t count = round(borderLength / diameter);
+ if (count % 2) {
+ count++;
+ }
+ mCount = count / 2 - 1;
+ if (mCount > 0) {
+ mBestOverlap = 1.0f - borderLength / (diameter * count);
+ }
+ } else {
+ mType = SINGLE_CURVE;
+ }
+ }
+
+ if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) {
+ Size cornerSize(centerCurveWidth, centerCurveHeight);
+ GetBezierPointsForCorner(&mCenterBezier, mCorner,
+ cornerPoint, cornerSize);
+ if (swapped) {
+ Swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]);
+ Swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]);
+ }
+ }
+
+ if (minR == 0.0f) {
+ mHasZeroBorderWidth = true;
+ }
+
+ if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
+ FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
+ }
+}
+
+
+bool
+DottedCornerFinder::HasMore(void) const
+{
+ if (mHasZeroBorderWidth) {
+ return mI < mMaxCount && mHasMore;
+ }
+
+ return mI < mCount;
+}
+
+DottedCornerFinder::Result
+DottedCornerFinder::Next(void)
+{
+ mI++;
+
+ if (mType == PERFECT) {
+ Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR;
+ if (mCorner == C_TL) {
+ phi = -M_PI / 2.0f - phi;
+ } else if (mCorner == C_TR) {
+ phi = -M_PI / 2.0f + phi;
+ } else if (mCorner == C_BR) {
+ phi = M_PI / 2.0f - phi;
+ } else {
+ phi = M_PI / 2.0f + phi;
+ }
+
+ Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
+ mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
+ return DottedCornerFinder::Result(C, mR0);
+ }
+
+ // Find unfilled and filled circles.
+ (void)FindNext(mBestOverlap);
+ if (mHasMore) {
+ (void)FindNext(mBestOverlap);
+ }
+
+ return Result(mLastC, mLastR);
+}
+
+void
+DottedCornerFinder::Reset(void)
+{
+ mLastC = mC0;
+ mLastR = mR0;
+ mLastT = 0.0f;
+ mHasMore = true;
+}
+
+void
+DottedCornerFinder::FindPointAndRadius(Point& C, Float& r,
+ const Point& innerTangent,
+ const Point& normal, Float t)
+{
+ // Find radius for the given tangent point on the inner curve such that the
+ // circle is also tangent to the outer curve.
+
+ NS_ASSERTION(mType == OTHER, "Wrong mType");
+
+ Float lower = 0.0f;
+ Float upper = mMaxR;
+ const Float DIST_MARGIN = 0.1f;
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ r = (upper + lower) / 2.0f;
+ C = innerTangent + normal * r;
+
+ Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
+ Float distSquare = (C - Near).LengthSquare();
+
+ if (distSquare > Square(r + DIST_MARGIN)) {
+ lower = r;
+ } else if (distSquare < Square(r - DIST_MARGIN)) {
+ upper = r;
+ } else {
+ break;
+ }
+ }
+}
+
+Float
+DottedCornerFinder::FindNext(Float overlap)
+{
+ Float lower = mLastT;
+ Float upper = 1.0f;
+ Float t;
+
+ Point C = mLastC;
+ Float r = 0.0f;
+
+ Float factor = (1.0f - overlap);
+
+ Float circlesDist = 0.0f;
+ Float expectedDist = 0.0f;
+
+ const Float DIST_MARGIN = 0.1f;
+ if (mType == SINGLE_CURVE_AND_RADIUS) {
+ r = mR0;
+
+ expectedDist = (r + mLastR) * factor;
+
+ // Find C_i on the center curve.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ C = GetBezierPoint(mCenterBezier, t);
+
+ // Check overlap along arc.
+ circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
+ if (circlesDist < expectedDist - DIST_MARGIN) {
+ lower = t;
+ } else if (circlesDist > expectedDist + DIST_MARGIN) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+ } else if (mType == SINGLE_CURVE) {
+ // Find C_i on the center curve, and calculate r_i.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ C = GetBezierPoint(mCenterBezier, t);
+
+ Point Diff = GetBezierDifferential(mCenterBezier, t);
+ Float DiffLength = Diff.Length();
+ if (DiffLength == 0.0f) {
+ // Basically this shouldn't happen.
+ // If differential is 0, we cannot calculate tangent circle,
+ // skip this point.
+ t = (t + upper) / 2.0f;
+ continue;
+ }
+
+ Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
+ r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
+ mInnerWidth, mInnerHeight);
+
+ // Check overlap along arc.
+ circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
+ expectedDist = (r + mLastR) * factor;
+ if (circlesDist < expectedDist - DIST_MARGIN) {
+ lower = t;
+ } else if (circlesDist > expectedDist + DIST_MARGIN) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+ } else {
+ Float distSquareMax = Square(mMaxR * 3.0f);
+ Float circlesDistSquare = 0.0f;
+
+ // Find C_i and r_i.
+ for (size_t i = 0; i < MAX_LOOP; i++) {
+ t = (upper + lower) / 2.0f;
+ Point innerTangent = GetBezierPoint(mInnerBezier, t);
+ if ((innerTangent - mLastC).LengthSquare() > distSquareMax) {
+ // It's clear that this tangent point is too far, skip it.
+ upper = t;
+ continue;
+ }
+
+ Point Diff = GetBezierDifferential(mInnerBezier, t);
+ Float DiffLength = Diff.Length();
+ if (DiffLength == 0.0f) {
+ // Basically this shouldn't happen.
+ // If differential is 0, we cannot calculate tangent circle,
+ // skip this point.
+ t = (t + upper) / 2.0f;
+ continue;
+ }
+
+ Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
+ FindPointAndRadius(C, r, innerTangent, normal, t);
+
+ // Check overlap with direct distance.
+ circlesDistSquare = (C - mLastC).LengthSquare();
+ expectedDist = (r + mLastR) * factor;
+ if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) {
+ lower = t;
+ } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) {
+ upper = t;
+ } else {
+ break;
+ }
+ }
+
+ circlesDist = sqrt(circlesDistSquare);
+ }
+
+ if (mHasZeroBorderWidth) {
+ // When calculating circle around r=0, it may result in wrong radius that
+ // is bigger than previous circle. Detect it and stop calculating.
+ const Float R_MARGIN = 0.1f;
+ if (mLastR < R_MARGIN && r > mLastR) {
+ mHasMore = false;
+ mLastR = 0.0f;
+ return 0.0f;
+ }
+ }
+
+ mLastT = t;
+ mLastC = C;
+ mLastR = r;
+
+ if (mHasZeroBorderWidth) {
+ const Float T_MARGIN = 0.001f;
+ if (mLastT >= 1.0f - T_MARGIN ||
+ (mLastC - mCn).LengthSquare() < Square(mLastR)) {
+ mHasMore = false;
+ }
+ }
+
+ if (expectedDist == 0.0f) {
+ return 0.0f;
+ }
+
+ return 1.0f - circlesDist * factor / expectedDist;
+}
+
+void
+DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius,
+ Float aMaxBorderRadius)
+{
+ // If overlap is not calculateable, find it with binary search,
+ // such that there exists i that C_i == C_n with the given overlap.
+
+ FourFloats key(aMinR, mMaxR,
+ aMinBorderRadius, aMaxBorderRadius);
+ BestOverlap best;
+ if (DottedCornerCache.Get(key, &best)) {
+ mCount = best.count;
+ mBestOverlap = best.overlap;
+ return;
+ }
+
+ Float lower = 0.0f;
+ Float upper = 0.5f;
+ // Start from lower bound to find the minimum number of circles.
+ Float overlap = 0.0f;
+ mBestOverlap = overlap;
+ size_t targetCount = 0;
+
+ const Float OVERLAP_MARGIN = 0.1f;
+ for (size_t j = 0; j < MAX_LOOP; j++) {
+ Reset();
+
+ size_t count;
+ Float actualOverlap;
+ if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
+ if (j == 0) {
+ mCount = mMaxCount;
+ break;
+ }
+ }
+
+ if (j == 0) {
+ if (count < 3 || (count == 3 && actualOverlap > 0.5f)) {
+ // |count == 3 && actualOverlap > 0.5f| means there could be
+ // a circle but it is too near from both ends.
+ //
+ // if actualOverlap == 0.0
+ // 1 2 3
+ // +-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ##### |
+ // |#######|*******|#######|#######|
+ // |###+###|***+***|###+###|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|# C_n #|
+ // | ##### | ***** | ##### | ##### |
+ // +-------+-------+-------+-------+
+ // |
+ // V
+ // +-------+---+-------+---+-------+
+ // | ##### | | ##### | | ##### |
+ // |#######| |#######| |#######|
+ // |###+###| |###+###| |###+###| Find the best overlap to place
+ // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them
+ // | ##### | | ##### | | ##### |
+ // +-------+---+-------+---|-------+
+ //
+ // if actualOverlap == 0.5
+ // 1 2 3
+ // +-------+-------+-------+---+
+ // | ##### | ***** | ##### |## |
+ // |#######|*******|##### C_n #|
+ // |###+###|***+***|###+###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|###|
+ // | ##### | ***** | ##### |## |
+ // +-------+-------+-------+---+
+ // |
+ // V
+ // +-------+-+-------+-+-------+
+ // | ##### | | ##### | | ##### |
+ // |#######| |#######| |#######|
+ // |###+###| |###+###| |###+###| Even if we place C_1 at the middle
+ // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
+ // | ##### | | ##### | | ##### |
+ // +-------+-+-------+-|-------+
+ // |
+ // V
+ // +-------+-----------+-------+
+ // | ##### | | ##### |
+ // |#######| |#######|
+ // |###+###| |###+###| Do not draw any circle
+ // |# C_0 #| |# C_n #|
+ // | ##### | | ##### |
+ // +-------+-----------+-------+
+ mCount = 0;
+ break;
+ }
+
+ // targetCount should be 2n, as we're searching C_1 to C_n.
+ //
+ // targetCount = 4
+ // mCount = 1
+ // 1 2 3 4
+ // +-------+-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ***** | ##### |
+ // |#######|*******|#######|*******|#######|
+ // |###+###|***+***|###+###|***+***|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
+ // | ##### | ***** | ##### | ***** | ##### |
+ // +-------+-------+-------+-------+-------+
+ // 1
+ //
+ // targetCount = 6
+ // mCount = 2
+ // 1 2 3 4 5 6
+ // +-------+-------+-------+-------+-------+-------+-------+
+ // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
+ // |#######|*******|#######|*******|#######|*******|#######|
+ // |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
+ // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
+ // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
+ // +-------+-------+-------+-------+-------+-------+-------+
+ // 1 2
+ if (count % 2) {
+ targetCount = count + 1;
+ } else {
+ targetCount = count;
+ }
+
+ mCount = targetCount / 2 - 1;
+ }
+
+ if (count == targetCount) {
+ mBestOverlap = overlap;
+
+ if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
+ break;
+ }
+
+ // We started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (actualOverlap > overlap) {
+ lower = overlap;
+ } else {
+ upper = overlap;
+ }
+ }
+ } else {
+ // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
+ // and we started from upper bound, no need to update range when j == 0.
+ if (j > 0) {
+ if (count > targetCount) {
+ upper = overlap;
+ } else {
+ lower = overlap;
+ }
+ }
+ }
+
+ overlap = (upper + lower) / 2.0f;
+ }
+
+ if (DottedCornerCache.Count() > DottedCornerCacheSize) {
+ DottedCornerCache.Clear();
+ }
+ DottedCornerCache.Put(key, BestOverlap(mBestOverlap, mCount));
+}
+
+bool
+DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap,
+ size_t* aCount,
+ Float* aActualOverlap)
+{
+ // Return the number of circles and the last circles' overlap for the
+ // given overlap.
+
+ Reset();
+
+ const Float T_MARGIN = 0.001f;
+ const Float DIST_MARGIN = 0.1f;
+ const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN);
+ for (size_t i = 0; i < mMaxCount; i++) {
+ Float actualOverlap = FindNext(aOverlap);
+ if (mLastT >= 1.0f - T_MARGIN ||
+ (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) {
+ *aCount = i + 1;
+ *aActualOverlap = actualOverlap;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/base/DottedCornerFinder.h b/layout/base/DottedCornerFinder.h
new file mode 100644
index 000000000..79eba5af8
--- /dev/null
+++ b/layout/base/DottedCornerFinder.h
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DottedCornerFinder_h_
+#define mozilla_DottedCornerFinder_h_
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+#include "gfxRect.h"
+
+namespace mozilla {
+
+// Calculate C_i and r_i for each filled/unfilled circles in dotted corner.
+// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n.
+//
+// ____-----------+
+// __---- ***** ###|
+// __---- ********* ####|
+// __--- ##### ***********#####|
+// _-- ######### *****+*****#####+ C_0
+// _- ########### *** C_1****#####|
+// / #####+##### ********* ####|
+// / . ### C_2 ### ***** ###|
+// | ######### ____-------+
+// | . #####____-----
+// | __----
+// | . /
+// | /
+// | ***** |
+// | ******* |
+// |*********|
+// |****+****|
+// | C_{n-1} |
+// | ******* |
+// | ***** |
+// | ##### |
+// | ####### |
+// |#########|
+// +----+----+
+// C_n
+
+class DottedCornerFinder
+{
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Size Size;
+
+public:
+ struct Result
+ {
+ // Center point of dot and its radius.
+ Point C;
+ Float r;
+
+ Result(const Point& aC, Float aR)
+ : C(aC), r(aR)
+ {
+ MOZ_ASSERT(aR >= 0);
+ }
+ };
+
+ // aBorderRadiusX
+ // aCornerDim.width
+ // |<----------------->|
+ // | | v
+ // --+-------------___---+--
+ // ^ | __-- | |
+ // | | _- | | aR0
+ // | | / aC0 +--
+ // | | / | ^
+ // | | | |
+ // aBorderRadiusY | | | __--+
+ // aCornerDim.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | aCn |
+ // --+----+----+
+ // | |
+ // |<-->|
+ // aRn
+ //
+ // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when
+ // aBorderRadiusX is smaller than aRn*2 or
+ // aBorderRadiusY is smaller than aR0*2.
+ //
+ // aCornerDim.width
+ // |<----------------->|
+ // | |
+ // | aBorderRadiusX |
+ // |<--------->| |
+ // | | |
+ // -------------------+-------__--+-------+--
+ // ^ ^ | _- | ^
+ // | | | / | |
+ // | | | / | |
+ // | aBorderRadiusY | | | | | aR0
+ // | | || | |
+ // | | | | |
+ // aCornerDim.height | v | | v
+ // | --+ aC0 +--
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // v | aCn |
+ // -------------------+---------+---------+
+ // | |
+ // |<------->|
+ // aRn
+ DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
+ mozilla::css::Corner aCorner,
+ Float aBorderRadiusX, Float aBorderRadiusY,
+ const Point& aC0, Float aR0, const Point& aCn, Float aRn,
+ const Size& aCornerDim);
+
+ bool HasMore(void) const;
+ Result Next(void);
+
+private:
+ static const size_t MAX_LOOP = 32;
+
+ // Bezier control points for the outer curve, the inner curve, and a curve
+ // that center points of circles are on (center curve).
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / __---+ center curve
+ // / __-- |
+ // | / |
+ // | / __--+ inner curve
+ // | | _-
+ // | | /
+ // | | /
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // +----+----+
+ Bezier mOuterBezier;
+ Bezier mInnerBezier;
+ Bezier mCenterBezier;
+
+ mozilla::css::Corner mCorner;
+
+ // Sign of the normal vector used in radius calculation, flipped depends on
+ // corner and start and end radii.
+ Float mNormalSign;
+
+ // Center points and raii for start and end circles, mR0 >= mRn.
+ // mMaxR = max(mR0, mRn)
+ //
+ // v
+ // ___---+------
+ // __-- #|# | mRn
+ // _- ##|## |
+ // / ##+## ---
+ // / mCn ^
+ // | #|#
+ // | __--+
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |## mC0 ##|
+ // | ####### |
+ // | ##### |
+ // | |
+ // |<-->|
+ //
+ // mR0
+ //
+ Point mC0;
+ Point mCn;
+ Float mR0;
+ Float mRn;
+ Float mMaxR;
+
+ // Parameters for the center curve with perfect circle and the inner curve.
+ // The center curve doesn't necessarily share the origin with others.
+ //
+ // ___---+
+ // __-- |
+ // _- |
+ // / __-+ |
+ // / __-- |
+ // | / |
+ // | / __--+--
+ // | | _- | ^
+ // | | / | |
+ // | | / | |
+ // | | | | |
+ // | | | | | mInnerHeight
+ // | | | | |
+ // | + | | |
+ // | | | v
+ // +---------+---------+
+ // | | mInnerCurveOrigin
+ // |<------->|
+ // mInnerWidth
+ //
+ // ___---+
+ // __--
+ // _-
+ // / __-+
+ // / __-- |
+ // | / |
+ // | / __--+
+ // | | _- |
+ // | | / |
+ // | | / |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | +--- | ------+
+ // | | | | mCenterCurveOrigin
+ // + | + |
+ // | |
+ // | |
+ // | |
+ // | |
+ // |<---------->|
+ // mCenterCurveR
+ //
+ Point mCenterCurveOrigin;
+ Float mCenterCurveR;
+ Point mInnerCurveOrigin;
+ Float mInnerWidth;
+ Float mInnerHeight;
+
+ Point mLastC;
+ Float mLastR;
+ Float mLastT;
+
+ // Overlap between two circles.
+ // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE,
+ // and direct distance on OTHER.
+ Float mBestOverlap;
+
+ // If one of border-widths is 0, do not calculate overlap, and draw circles
+ // until it reaches the other side or exceeds mMaxCount.
+ bool mHasZeroBorderWidth;
+ bool mHasMore;
+
+ // The maximum number of filled/unfilled circles.
+ size_t mMaxCount;
+
+ enum {
+ // radius.width
+ // |<----------------->|
+ // | |
+ // --+-------------___---+----
+ // ^ | __-- #|# ^
+ // | | _- ##|## |
+ // | | / ##+## | top-width
+ // | | / ##|## |
+ // | | | #|# v
+ // | | | __--+----
+ // radius.height | || _-
+ // | || /
+ // | | /
+ // | | |
+ // | | |
+ // | | ##### |
+ // | | ####### |
+ // v |#########|
+ // --+----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ // |<------->|
+ // left-width
+
+ // * top-width == left-width
+ // * radius.width == radius.height
+ // * top-width < radius.width * 2
+ //
+ // All circles has same radii and are on single perfect circle's arc.
+ // Overlap is known.
+ //
+ // Split the perfect circle's arc into 2n segments, each segment's length is
+ // top-width * (1 - overlap). Place each circle's center point C_i on each
+ // end of the segment, each circle's radius r_i is top-width / 2
+ //
+ // #####
+ // #######
+ // perfect #########
+ // circle's ___---+####
+ // arc ##### __-- ## C_0 ##
+ // | #####_- ###|###
+ // | ####+#### ##|##
+ // | ##/C_i ## |
+ // | |###### |
+ // | | ##### |
+ // +->| |
+ // | |
+ // ##|## |
+ // ###|### |
+ // ####|#### |
+ // ####+-------------------+
+ // ## C_n ##
+ // #######
+ // #####
+ PERFECT,
+
+ // * top-width == left-width
+ // * 0.5 < radius.width / radius.height < 2.0
+ // * top-width < min(radius.width, radius.height) * 2
+ //
+ // All circles has same radii and are on single elliptic arc.
+ // Overlap is known.
+ //
+ // Split the elliptic arc into 2n segments, each segment's length is
+ // top-width * (1 - overlap). Place each circle's center point C_i on each
+ // end of the segment, each circle's radius r_i is top-width / 2
+ //
+ // #####
+ // #######
+ // ##### #########
+ // ####### ____----+####
+ // elliptic ######__--- ## C_0 ##
+ // arc ##__+-### ###|###
+ // | / # C_i # ##|##
+ // +--> / ##### |
+ // | |
+ // ###|# |
+ // ###|### |
+ // ####|#### |
+ // ####+------------------------+
+ // ## C_n ##
+ // #######
+ // #####
+ SINGLE_CURVE_AND_RADIUS,
+
+ // * top-width != left-width
+ // * 0 < min(top-width, left-width)
+ // * 0.5 < radius.width / radius.height < 2.0
+ // * max(top-width, left-width) < min(radius.width, radius.height) * 2
+ //
+ // All circles are on single elliptic arc.
+ // Overlap is unknown.
+ //
+ // Place each circle's center point C_i on elliptic arc, each circle's
+ // radius r_i is the distance between the center point and the inner curve.
+ // The arc segment's length between C_i and C_{i-1} is
+ // (r_i + r_{i-1}) * (1 - overlap).
+ //
+ // outer curve
+ // /
+ // /
+ // / / center curve
+ // / ####### /
+ // /## /#
+ // +# / #
+ // /# / #
+ // / # C_i / #
+ // / # + # /
+ // / # / \ # / inner curve
+ // # / \ #/
+ // # / r_i \+
+ // #/ ##/
+ // / ####### /
+ // /
+ SINGLE_CURVE,
+
+ // Other cases.
+ // Circles are not on single elliptic arc.
+ // Overlap are unknown.
+ //
+ // Place tangent point innerTangent on the inner curve and find circle's
+ // center point C_i and radius r_i where the circle is also tangent to the
+ // outer curve.
+ // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap).
+ //
+ // outer curve
+ // /
+ // /
+ // /
+ // / #######
+ // /## ##
+ // +# #
+ // /# \ #
+ // / # \ #
+ // / # + # /
+ // / # C_i \ # / inner curve
+ // # \ #/
+ // # r_i \+
+ // ## ##/ innerTangent
+ // ####### /
+ // /
+ OTHER
+ } mType;
+
+ size_t mI;
+ size_t mCount;
+
+ // Determine mType from parameters.
+ void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY);
+
+ // Reset calculation.
+ void Reset(void);
+
+ // Find radius for the given tangent point on the inner curve such that the
+ // circle is also tangent to the outer curve.
+ void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent,
+ const Point& normal, Float t);
+
+ // Find next dot.
+ Float FindNext(Float overlap);
+
+ // Find mBestOverlap for parameters.
+ void FindBestOverlap(Float aMinR,
+ Float aMinBorderRadius, Float aMaxBorderRadius);
+
+ // Fill corner with dots with given overlap, and return the number of dots
+ // and last two dots's overlap.
+ bool GetCountAndLastOverlap(Float aOverlap,
+ size_t* aCount, Float* aActualOverlap);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_DottedCornerFinder_h_ */
diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp
new file mode 100644
index 000000000..9269c2ab6
--- /dev/null
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -0,0 +1,6378 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "FrameLayerBuilder.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/gfx/Matrix.h"
+#include "ActiveLayerTracker.h"
+#include "BasicLayers.h"
+#include "DisplayItemScrollClip.h"
+#include "ImageContainer.h"
+#include "ImageLayers.h"
+#include "LayerTreeInvalidation.h"
+#include "Layers.h"
+#include "LayerUserData.h"
+#include "MaskLayerImageCache.h"
+#include "UnitTransforms.h"
+#include "Units.h"
+#include "gfx2DGlue.h"
+#include "gfxEnv.h"
+#include "gfxUtils.h"
+#include "nsAutoPtr.h"
+#include "nsAnimationManager.h"
+#include "nsDisplayList.h"
+#include "nsDocShell.h"
+#include "nsIScrollableFrame.h"
+#include "nsImageFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsRenderingContext.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsTransitionManager.h"
+#include "mozilla/LayerTimelineMarker.h"
+
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/Move.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TextureWrapperImage.h"
+#include "mozilla/Unused.h"
+#include "GeckoProfiler.h"
+#include "LayersLogging.h"
+#include "gfxPrefs.h"
+
+#include <algorithm>
+
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+class PaintedDisplayItemLayerUserData;
+
+static nsTHashtable<nsPtrHashKey<FrameLayerBuilder::DisplayItemData>>* sAliveDisplayItemDatas;
+
+/**
+ * The address of gPaintedDisplayItemLayerUserData is used as the user
+ * data key for PaintedLayers created by FrameLayerBuilder.
+ * It identifies PaintedLayers used to draw non-layer content, which are
+ * therefore eligible for recycling. We want display items to be able to
+ * create their own dedicated PaintedLayers in BuildLayer, if necessary,
+ * and we wouldn't want to accidentally recycle those.
+ * The user data is a PaintedDisplayItemLayerUserData.
+ */
+uint8_t gPaintedDisplayItemLayerUserData;
+/**
+ * The address of gColorLayerUserData is used as the user
+ * data key for ColorLayers created by FrameLayerBuilder.
+ * The user data is null.
+ */
+uint8_t gColorLayerUserData;
+/**
+ * The address of gImageLayerUserData is used as the user
+ * data key for ImageLayers created by FrameLayerBuilder.
+ * The user data is null.
+ */
+uint8_t gImageLayerUserData;
+/**
+ * The address of gLayerManagerUserData is used as the user
+ * data key for retained LayerManagers managed by FrameLayerBuilder.
+ * The user data is a LayerManagerData.
+ */
+uint8_t gLayerManagerUserData;
+/**
+ * The address of gMaskLayerUserData is used as the user
+ * data key for mask layers managed by FrameLayerBuilder.
+ * The user data is a MaskLayerUserData.
+ */
+uint8_t gMaskLayerUserData;
+/**
+ * The address of gCSSMaskLayerUserData is used as the user
+ * data key for mask layers of css masking managed by FrameLayerBuilder.
+ * The user data is a CSSMaskLayerUserData.
+ */
+uint8_t gCSSMaskLayerUserData;
+
+// a global cache of image containers used for mask layers
+static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
+
+static inline MaskLayerImageCache* GetMaskLayerImageCache()
+{
+ if (!gMaskLayerImageCache) {
+ gMaskLayerImageCache = new MaskLayerImageCache();
+ }
+
+ return gMaskLayerImageCache;
+}
+
+FrameLayerBuilder::FrameLayerBuilder()
+ : mRetainingManager(nullptr)
+ , mDetectedDOMModification(false)
+ , mInvalidateAllLayers(false)
+ , mInLayerTreeCompressionMode(false)
+ , mContainerLayerGeneration(0)
+ , mMaxContainerLayerGeneration(0)
+{
+ MOZ_COUNT_CTOR(FrameLayerBuilder);
+}
+
+FrameLayerBuilder::~FrameLayerBuilder()
+{
+ GetMaskLayerImageCache()->Sweep();
+ MOZ_COUNT_DTOR(FrameLayerBuilder);
+}
+
+FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
+ Layer* aLayer, nsIFrame* aFrame)
+
+ : mParent(aParent)
+ , mLayer(aLayer)
+ , mDisplayItemKey(aKey)
+ , mItem(nullptr)
+ , mUsed(true)
+ , mIsInvalid(false)
+{
+ MOZ_COUNT_CTOR(FrameLayerBuilder::DisplayItemData);
+
+ if (!sAliveDisplayItemDatas) {
+ sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<FrameLayerBuilder::DisplayItemData>>();
+ }
+ MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this));
+ sAliveDisplayItemDatas->PutEntry(this);
+
+ MOZ_RELEASE_ASSERT(mLayer);
+ if (aFrame) {
+ AddFrame(aFrame);
+ }
+
+}
+
+void
+FrameLayerBuilder::DisplayItemData::AddFrame(nsIFrame* aFrame)
+{
+ MOZ_RELEASE_ASSERT(mLayer);
+ mFrameList.AppendElement(aFrame);
+
+ nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty());
+ if (!array) {
+ array = new nsTArray<DisplayItemData*>();
+ aFrame->Properties().Set(FrameLayerBuilder::LayerManagerDataProperty(), array);
+ }
+ array->AppendElement(this);
+}
+
+void
+FrameLayerBuilder::DisplayItemData::RemoveFrame(nsIFrame* aFrame)
+{
+ MOZ_RELEASE_ASSERT(mLayer);
+ bool result = mFrameList.RemoveElement(aFrame);
+ MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!");
+
+ nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty());
+ MOZ_RELEASE_ASSERT(array, "Must be already stored on the frame!");
+ array->RemoveElement(this);
+}
+
+void
+FrameLayerBuilder::DisplayItemData::EndUpdate()
+{
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_ASSERT(!mItem);
+ mIsInvalid = false;
+ mUsed = false;
+}
+
+void
+FrameLayerBuilder::DisplayItemData::EndUpdate(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
+{
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_ASSERT(mItem);
+ MOZ_ASSERT(mGeometry || aGeometry);
+
+ if (aGeometry) {
+ mGeometry = aGeometry;
+ }
+ mClip = mItem->GetClip();
+ mFrameListChanges.Clear();
+
+ mItem = nullptr;
+ EndUpdate();
+}
+
+void
+FrameLayerBuilder::DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
+ uint32_t aContainerLayerGeneration,
+ nsDisplayItem* aItem /* = nullptr */)
+{
+ MOZ_RELEASE_ASSERT(mLayer);
+ MOZ_RELEASE_ASSERT(aLayer);
+ mLayer = aLayer;
+ mOptLayer = nullptr;
+ mInactiveManager = nullptr;
+ mLayerState = aState;
+ mContainerLayerGeneration = aContainerLayerGeneration;
+ mUsed = true;
+
+ if (aLayer->AsPaintedLayer()) {
+ mItem = aItem;
+ }
+
+ if (!aItem) {
+ return;
+ }
+
+ // We avoid adding or removing element unnecessarily
+ // since we have to modify userdata each time
+ AutoTArray<nsIFrame*, 4> copy(mFrameList);
+ if (!copy.RemoveElement(aItem->Frame())) {
+ AddFrame(aItem->Frame());
+ mFrameListChanges.AppendElement(aItem->Frame());
+ }
+
+ AutoTArray<nsIFrame*,4> mergedFrames;
+ aItem->GetMergedFrames(&mergedFrames);
+ for (uint32_t i = 0; i < mergedFrames.Length(); ++i) {
+ if (!copy.RemoveElement(mergedFrames[i])) {
+ AddFrame(mergedFrames[i]);
+ mFrameListChanges.AppendElement(mergedFrames[i]);
+ }
+ }
+
+ for (uint32_t i = 0; i < copy.Length(); i++) {
+ RemoveFrame(copy[i]);
+ mFrameListChanges.AppendElement(copy[i]);
+ }
+}
+
+static const nsIFrame* sDestroyedFrame = nullptr;
+FrameLayerBuilder::DisplayItemData::~DisplayItemData()
+{
+ MOZ_COUNT_DTOR(FrameLayerBuilder::DisplayItemData);
+ MOZ_RELEASE_ASSERT(mLayer);
+ for (uint32_t i = 0; i < mFrameList.Length(); i++) {
+ nsIFrame* frame = mFrameList[i];
+ if (frame == sDestroyedFrame) {
+ continue;
+ }
+ nsTArray<DisplayItemData*> *array =
+ reinterpret_cast<nsTArray<DisplayItemData*>*>(frame->Properties().Get(LayerManagerDataProperty()));
+ array->RemoveElement(this);
+ }
+
+ MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas && sAliveDisplayItemDatas->Contains(this));
+ sAliveDisplayItemDatas->RemoveEntry(this);
+ if (sAliveDisplayItemDatas->Count() == 0) {
+ delete sAliveDisplayItemDatas;
+ sAliveDisplayItemDatas = nullptr;
+ }
+}
+
+void
+FrameLayerBuilder::DisplayItemData::ClearAnimationCompositorState()
+{
+ if (mDisplayItemKey != nsDisplayItem::TYPE_TRANSFORM &&
+ mDisplayItemKey != nsDisplayItem::TYPE_OPACITY) {
+ return;
+ }
+
+ for (nsIFrame* frame : mFrameList) {
+ nsCSSPropertyID prop = mDisplayItemKey == nsDisplayItem::TYPE_TRANSFORM ?
+ eCSSProperty_transform : eCSSProperty_opacity;
+ EffectCompositor::ClearIsRunningOnCompositor(frame, prop);
+ }
+}
+
+const nsTArray<nsIFrame*>&
+FrameLayerBuilder::DisplayItemData::GetFrameListChanges()
+{
+ return mFrameListChanges;
+}
+
+/**
+ * This is the userdata we associate with a layer manager.
+ */
+class LayerManagerData : public LayerUserData {
+public:
+ explicit LayerManagerData(LayerManager *aManager)
+ : mLayerManager(aManager)
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ , mParent(nullptr)
+#endif
+ , mInvalidateAllLayers(false)
+ {
+ MOZ_COUNT_CTOR(LayerManagerData);
+ }
+ ~LayerManagerData() {
+ MOZ_COUNT_DTOR(LayerManagerData);
+ }
+
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ void Dump(const char *aPrefix = "") {
+ printf_stderr("%sLayerManagerData %p\n", aPrefix, this);
+
+ for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
+ FrameLayerBuilder::DisplayItemData* data = iter.Get()->GetKey();
+
+ nsAutoCString prefix;
+ prefix += aPrefix;
+ prefix += " ";
+
+ const char* layerState;
+ switch (data->mLayerState) {
+ case LAYER_NONE:
+ layerState = "LAYER_NONE"; break;
+ case LAYER_INACTIVE:
+ layerState = "LAYER_INACTIVE"; break;
+ case LAYER_ACTIVE:
+ layerState = "LAYER_ACTIVE"; break;
+ case LAYER_ACTIVE_FORCE:
+ layerState = "LAYER_ACTIVE_FORCE"; break;
+ case LAYER_ACTIVE_EMPTY:
+ layerState = "LAYER_ACTIVE_EMPTY"; break;
+ case LAYER_SVG_EFFECTS:
+ layerState = "LAYER_SVG_EFFECTS"; break;
+ }
+ uint32_t mask = (1 << nsDisplayItem::TYPE_BITS) - 1;
+
+ nsAutoCString str;
+ str += prefix;
+ str += nsPrintfCString("Frame %p ", data->mFrameList[0]);
+ str += nsDisplayItem::DisplayItemTypeName(static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask));
+ if ((data->mDisplayItemKey >> nsDisplayItem::TYPE_BITS)) {
+ str += nsPrintfCString("(%i)", data->mDisplayItemKey >> nsDisplayItem::TYPE_BITS);
+ }
+ str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get());
+ if (data->mOptLayer) {
+ str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get());
+ }
+ if (data->mInactiveManager) {
+ str += nsPrintfCString(", InactiveLayerManager %p", data->mInactiveManager.get());
+ }
+ str += "\n";
+
+ printf_stderr("%s", str.get());
+
+ if (data->mInactiveManager) {
+ prefix += " ";
+ printf_stderr("%sDumping inactive layer info:\n", prefix.get());
+ LayerManagerData* lmd = static_cast<LayerManagerData*>
+ (data->mInactiveManager->GetUserData(&gLayerManagerUserData));
+ lmd->Dump(prefix.get());
+ }
+ }
+ }
+#endif
+
+ /**
+ * Tracks which frames have layers associated with them.
+ */
+ LayerManager *mLayerManager;
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ LayerManagerData *mParent;
+#endif
+ nsTHashtable<nsRefPtrHashKey<FrameLayerBuilder::DisplayItemData> > mDisplayItems;
+ bool mInvalidateAllLayers;
+};
+
+/* static */ void
+FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame)
+{
+ FrameProperties props = aFrame->Properties();
+ props.Delete(LayerManagerDataProperty());
+}
+
+struct AssignedDisplayItem
+{
+ AssignedDisplayItem(nsDisplayItem* aItem,
+ const DisplayItemClip& aClip,
+ LayerState aLayerState)
+ : mItem(aItem)
+ , mClip(aClip)
+ , mLayerState(aLayerState)
+ {}
+
+ nsDisplayItem* mItem;
+ DisplayItemClip mClip;
+ LayerState mLayerState;
+};
+
+/**
+ * We keep a stack of these to represent the PaintedLayers that are
+ * currently available to have display items added to.
+ * We use a stack here because as much as possible we want to
+ * assign display items to existing PaintedLayers, and to the lowest
+ * PaintedLayer in z-order. This reduces the number of layers and
+ * makes it more likely a display item will be rendered to an opaque
+ * layer, giving us the best chance of getting subpixel AA.
+ */
+class PaintedLayerData {
+public:
+ PaintedLayerData() :
+ mAnimatedGeometryRoot(nullptr),
+ mScrollClip(nullptr),
+ mReferenceFrame(nullptr),
+ mLayer(nullptr),
+ mSolidColor(NS_RGBA(0, 0, 0, 0)),
+ mIsSolidColorInVisibleRegion(false),
+ mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)),
+ mSingleItemFixedToViewport(false),
+ mNeedComponentAlpha(false),
+ mForceTransparentSurface(false),
+ mHideAllLayersBelow(false),
+ mOpaqueForAnimatedGeometryRootParent(false),
+ mDisableFlattening(false),
+ mBackfaceHidden(false),
+ mImage(nullptr),
+ mCommonClipCount(-1),
+ mNewChildLayersIndex(-1)
+ {}
+
+#ifdef MOZ_DUMP_PAINTING
+ /**
+ * Keep track of important decisions for debugging.
+ */
+ nsCString mLog;
+
+ #define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \
+ if (gfxPrefs::LayersDumpDecision()) { \
+ pld->mLog.AppendPrintf("\t\t\t\t"); \
+ pld->mLog.AppendPrintf(__VA_ARGS__); \
+ }
+#else
+ #define FLB_LOG_PAINTED_LAYER_DECISION(...)
+#endif
+
+ /**
+ * Record that an item has been added to the PaintedLayer, so we
+ * need to update our regions.
+ * @param aVisibleRect the area of the item that's visible
+ * @param aSolidColor if non-null, the visible area of the item is
+ * a constant color given by *aSolidColor
+ */
+ void Accumulate(ContainerState* aState,
+ nsDisplayItem* aItem,
+ const nsIntRegion& aClippedOpaqueRegion,
+ const nsIntRect& aVisibleRect,
+ const DisplayItemClip& aClip,
+ LayerState aLayerState);
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; }
+
+ /**
+ * A region including the horizontal pan, vertical pan, and no action regions.
+ */
+ nsRegion CombinedTouchActionRegion();
+
+ /**
+ * Add the given hit regions to the hit regions to the hit retions for this
+ * PaintedLayer.
+ */
+ void AccumulateEventRegions(ContainerState* aState, nsDisplayLayerEventRegions* aEventRegions);
+
+ /**
+ * If this represents only a nsDisplayImage, and the image type supports being
+ * optimized to an ImageLayer, returns true.
+ */
+ bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * If this represents only a nsDisplayImage, and the image type supports being
+ * optimized to an ImageLayer, returns an ImageContainer for the underlying
+ * image if one is available.
+ */
+ already_AddRefed<ImageContainer> GetContainerForImageLayer(nsDisplayListBuilder* aBuilder);
+
+ bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const
+ { return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty(); }
+ bool VisibleRegionIntersects(const nsIntRegion& aRegion) const
+ { return !mVisibleRegion.Intersect(aRegion).IsEmpty(); }
+
+ /**
+ * The region of visible content in the layer, relative to the
+ * container layer (which is at the snapped top-left of the display
+ * list reference frame).
+ */
+ nsIntRegion mVisibleRegion;
+ /**
+ * The region of visible content in the layer that is opaque.
+ * Same coordinate system as mVisibleRegion.
+ */
+ nsIntRegion mOpaqueRegion;
+ /**
+ * The definitely-hit region for this PaintedLayer.
+ */
+ nsRegion mHitRegion;
+ /**
+ * The maybe-hit region for this PaintedLayer.
+ */
+ nsRegion mMaybeHitRegion;
+ /**
+ * The dispatch-to-content hit region for this PaintedLayer.
+ */
+ nsRegion mDispatchToContentHitRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events
+ * but disallows panning and zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mNoActionRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events and
+ * allows horizontal panning but not zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mHorizontalPanRegion;
+ /**
+ * The region for this PaintedLayer that is sensitive to events and
+ * allows vertical panning but not zooming. This is an approximation
+ * and any deviation from the true region will be part of the
+ * mDispatchToContentHitRegion.
+ */
+ nsRegion mVerticalPanRegion;
+ /**
+ * Scaled versions of the bounds of mHitRegion and mMaybeHitRegion.
+ * We store these because FindPaintedLayerFor() needs to consume them
+ * in this form, and it's a hot code path so we don't want to scale
+ * them inside that function.
+ */
+ nsIntRect mScaledHitRegionBounds;
+ nsIntRect mScaledMaybeHitRegionBounds;
+ /**
+ * The "active scrolled root" for all content in the layer. Must
+ * be non-null; all content in a PaintedLayer must have the same
+ * active scrolled root.
+ */
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ /**
+ * The scroll clip for this layer.
+ */
+ const DisplayItemScrollClip* mScrollClip;
+ /**
+ * The offset between mAnimatedGeometryRoot and the reference frame.
+ */
+ nsPoint mAnimatedGeometryRootOffset;
+ /**
+ * If non-null, the frame from which we'll extract "fixed positioning"
+ * metadata for this layer. This can be a position:fixed frame or a viewport
+ * frame; the latter case is used for background-attachment:fixed content.
+ */
+ const nsIFrame* mReferenceFrame;
+ PaintedLayer* mLayer;
+ /**
+ * If mIsSolidColorInVisibleRegion is true, this is the color of the visible
+ * region.
+ */
+ nscolor mSolidColor;
+ /**
+ * True if every pixel in mVisibleRegion will have color mSolidColor.
+ */
+ bool mIsSolidColorInVisibleRegion;
+ /**
+ * The target background color for smoothing fonts that are drawn on top of
+ * transparent parts of the layer.
+ */
+ nscolor mFontSmoothingBackgroundColor;
+ /**
+ * True if the layer contains exactly one item that returned true for
+ * ShouldFixToViewport.
+ */
+ bool mSingleItemFixedToViewport;
+ /**
+ * True if there is any text visible in the layer that's over
+ * transparent pixels in the layer.
+ */
+ bool mNeedComponentAlpha;
+ /**
+ * Set if the layer should be treated as transparent, even if its entire
+ * area is covered by opaque display items. For example, this needs to
+ * be set if something is going to "punch holes" in the layer by clearing
+ * part of its surface.
+ */
+ bool mForceTransparentSurface;
+ /**
+ * Set if all layers below this PaintedLayer should be hidden.
+ */
+ bool mHideAllLayersBelow;
+ /**
+ * Set if the opaque region for this layer can be applied to the parent
+ * animated geometry root of this layer's animated geometry root.
+ * We set this when a PaintedLayer's animated geometry root is a scrollframe
+ * and the PaintedLayer completely fills the displayport of the scrollframe.
+ */
+ bool mOpaqueForAnimatedGeometryRootParent;
+ /**
+ * Set if there is content in the layer that must avoid being flattened.
+ */
+ bool mDisableFlattening;
+ /**
+ * Set if the backface of this region is hidden to the user.
+ * Content that backface is hidden should not be draw on the layer
+ * with visible backface.
+ */
+ bool mBackfaceHidden;
+ /**
+ * Stores the pointer to the nsDisplayImage if we want to
+ * convert this to an ImageLayer.
+ */
+ nsDisplayImageContainer* mImage;
+ /**
+ * Stores the clip that we need to apply to the image or, if there is no
+ * image, a clip for SOME item in the layer. There is no guarantee which
+ * item's clip will be stored here and mItemClip should not be used to clip
+ * the whole layer - only some part of the clip should be used, as determined
+ * by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even be
+ * no part at all.
+ */
+ DisplayItemClip mItemClip;
+ /**
+ * The first mCommonClipCount rounded rectangle clips are identical for
+ * all items in the layer.
+ * -1 if there are no items in the layer; must be >=0 by the time that this
+ * data is popped from the stack.
+ */
+ int32_t mCommonClipCount;
+ /**
+ * Index of this layer in mNewChildLayers.
+ */
+ int32_t mNewChildLayersIndex;
+ /*
+ * Updates mCommonClipCount by checking for rounded rect clips in common
+ * between the clip on a new item (aCurrentClip) and the common clips
+ * on items already in the layer (the first mCommonClipCount rounded rects
+ * in mItemClip).
+ */
+ void UpdateCommonClipCount(const DisplayItemClip& aCurrentClip);
+ /**
+ * The union of all the bounds of the display items in this layer.
+ */
+ nsIntRect mBounds;
+ /**
+ * The region of visible content above the layer and below the
+ * next PaintedLayerData currently in the stack, if any.
+ * This is a conservative approximation: it contains the true region.
+ */
+ nsIntRegion mVisibleAboveRegion;
+ /**
+ * All the display items that have been assigned to this painted layer.
+ * These items get added by Accumulate().
+ */
+ nsTArray<AssignedDisplayItem> mAssignedDisplayItems;
+
+};
+
+struct NewLayerEntry {
+ NewLayerEntry()
+ : mAnimatedGeometryRoot(nullptr)
+ , mScrollClip(nullptr)
+ , mLayerContentsVisibleRect(0, 0, -1, -1)
+ , mLayerState(LAYER_INACTIVE)
+ , mHideAllLayersBelow(false)
+ , mOpaqueForAnimatedGeometryRootParent(false)
+ , mPropagateComponentAlphaFlattening(true)
+ , mUntransformedVisibleRegion(false)
+ {}
+ // mLayer is null if the previous entry is for a PaintedLayer that hasn't
+ // been optimized to some other form (yet).
+ RefPtr<Layer> mLayer;
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ const DisplayItemScrollClip* mScrollClip;
+ // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
+ // on the layer.
+ UniquePtr<ScrollMetadata> mBaseScrollMetadata;
+ // The following are only used for retained layers (for occlusion
+ // culling of those layers). These regions are all relative to the
+ // container reference frame.
+ nsIntRegion mVisibleRegion;
+ nsIntRegion mOpaqueRegion;
+ // This rect is in the layer's own coordinate space. The computed visible
+ // region for the layer cannot extend beyond this rect.
+ nsIntRect mLayerContentsVisibleRect;
+ LayerState mLayerState;
+ bool mHideAllLayersBelow;
+ // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of
+ // this layer is opaque in the same position even subject to the animation of
+ // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot
+ // is a scrolled frame and the scrolled content is opaque everywhere in the
+ // displayport, we can set this flag.
+ // When this flag is set, we can treat this opaque region as covering
+ // content whose animated geometry root is the animated geometry root for
+ // mAnimatedGeometryRoot->GetParent().
+ bool mOpaqueForAnimatedGeometryRootParent;
+
+ // If true, then the content flags for this layer should contribute
+ // to our decision to flatten component alpha layers, false otherwise.
+ bool mPropagateComponentAlphaFlattening;
+ // mVisibleRegion is relative to the associated frame before
+ // transform.
+ bool mUntransformedVisibleRegion;
+};
+
+class PaintedLayerDataTree;
+
+/**
+ * This is tree node type for PaintedLayerDataTree.
+ * Each node corresponds to a different animated geometry root, and contains
+ * a stack of PaintedLayerDatas, in bottom-to-top order.
+ * There is at most one node per animated geometry root. The ancestor and
+ * descendant relations in PaintedLayerDataTree tree mirror those in the frame
+ * tree.
+ * Each node can have clip that describes the potential extents that items in
+ * this node can cover. If mHasClip is false, it means that the node's contents
+ * can move anywhere.
+ * Testing against the clip instead of the node's actual contents has the
+ * advantage that the node's contents can move or animate without affecting
+ * content in other nodes. So we don't need to re-layerize during animations
+ * (sync or async), and during async animations everything is guaranteed to
+ * look correct.
+ * The contents of a node's PaintedLayerData stack all share the node's
+ * animated geometry root. The child nodes are on top of the PaintedLayerData
+ * stack, in z-order, and the clip rects of the child nodes are allowed to
+ * intersect with the visible region or visible above region of their parent
+ * node's PaintedLayerDatas.
+ */
+class PaintedLayerDataNode {
+public:
+ PaintedLayerDataNode(PaintedLayerDataTree& aTree,
+ PaintedLayerDataNode* aParent,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot);
+ ~PaintedLayerDataNode();
+
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() const { return mAnimatedGeometryRoot; }
+
+ /**
+ * Whether this node's contents can potentially intersect aRect.
+ * aRect is in our tree's ContainerState's coordinate space.
+ */
+ bool Intersects(const nsIntRect& aRect) const
+ { return !mHasClip || mClipRect.Intersects(aRect); }
+
+ /**
+ * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our
+ * children, and return it.
+ */
+ PaintedLayerDataNode* AddChildNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be
+ * added to. Creates a new PaintedLayerData by calling
+ * aNewPaintedLayerCallback if necessary.
+ */
+ template<typename NewPaintedLayerCallbackType>
+ PaintedLayerData* FindPaintedLayerFor(const nsIntRect& aVisibleRect,
+ bool aBackfaceHidden,
+ const DisplayItemScrollClip* aScrollClip,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+ /**
+ * Find an opaque background color for aRegion. Pulls a color from the parent
+ * geometry root if appropriate, but only if that color is present underneath
+ * the whole clip of this node, so that this node's contents can animate or
+ * move (possibly async) without having to change the background color.
+ * @param aUnderIndex Searching will start in mPaintedLayerDataStack right
+ * below aUnderIndex.
+ */
+ enum { ABOVE_TOP = -1 };
+ nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion,
+ int32_t aUnderIndex = ABOVE_TOP) const;
+ /**
+ * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely
+ * nothing is in between, so that it can be used for a layer that can move
+ * anywhere inside our clip.
+ */
+ nscolor FindOpaqueBackgroundColorCoveringEverything() const;
+
+ /**
+ * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion,
+ * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty.
+ */
+ void AddToVisibleAboveRegion(const nsIntRect& aRect);
+ /**
+ * Call this if all of our existing content can potentially be covered, so
+ * nothing can merge with it and all new content needs to create new items
+ * on top. This will finish all of our children and pop our whole
+ * mPaintedLayerDataStack.
+ */
+ void SetAllDrawingAbove();
+
+ /**
+ * Finish this node: Finish all children, finish our PaintedLayer contents,
+ * and (if requested) adjust our parent's visible above region to include
+ * our clip.
+ */
+ void Finish(bool aParentNeedsAccurateVisibleAboveRegion);
+
+ /**
+ * Finish any children that intersect aRect.
+ */
+ void FinishChildrenIntersecting(const nsIntRect& aRect);
+
+ /**
+ * Finish all children.
+ */
+ void FinishAllChildren() { FinishAllChildren(true); }
+
+protected:
+ /**
+ * Finish the topmost item in mPaintedLayerDataStack and pop it from the
+ * stack.
+ */
+ void PopPaintedLayerData();
+ /**
+ * Finish all items in mPaintedLayerDataStack and clear the stack.
+ */
+ void PopAllPaintedLayerData();
+ /**
+ * Finish all of our child nodes, but don't touch mPaintedLayerDataStack.
+ */
+ void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion);
+ /**
+ * Pass off opaque background color searching to our parent node, if we have
+ * one.
+ */
+ nscolor FindOpaqueBackgroundColorInParentNode() const;
+
+ PaintedLayerDataTree& mTree;
+ PaintedLayerDataNode* mParent;
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+
+ /**
+ * Our contents: a PaintedLayerData stack and our child nodes.
+ */
+ nsTArray<PaintedLayerData> mPaintedLayerDataStack;
+
+ /**
+ * UniquePtr is used here in the sense of "unique ownership", i.e. there is
+ * only one owner. Not in the sense of "this is the only pointer to the
+ * node": There are two other, non-owning, pointers to our child nodes: The
+ * node's respective children point to their parent node with their mParent
+ * pointer, and the tree keeps a map of animated geometry root to node in its
+ * mNodes member. These outside pointers are the reason that mChildren isn't
+ * just an nsTArray<PaintedLayerDataNode> (since the pointers would become
+ * invalid whenever the array expands its capacity).
+ */
+ nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren;
+
+ /**
+ * The region that's covered between our "background" and the bottom of
+ * mPaintedLayerDataStack. This is used to indicate whether we can pull
+ * a background color from our parent node. If mVisibleAboveBackgroundRegion
+ * should be considered infinite, mAllDrawingAboveBackground will be true and
+ * the value of mVisibleAboveBackgroundRegion will be meaningless.
+ */
+ nsIntRegion mVisibleAboveBackgroundRegion;
+
+ /**
+ * Our clip, if we have any. If not, that means we can move anywhere, and
+ * mHasClip will be false and mClipRect will be meaningless.
+ */
+ nsIntRect mClipRect;
+ bool mHasClip;
+
+ /**
+ * Whether mVisibleAboveBackgroundRegion should be considered infinite.
+ */
+ bool mAllDrawingAboveBackground;
+};
+
+class ContainerState;
+
+/**
+ * A tree of PaintedLayerDataNodes. At any point in time, the tree only
+ * contains nodes for animated geometry roots that new items can potentially
+ * merge into. Any time content is added on top that overlaps existing things
+ * in such a way that we no longer want to merge new items with some existing
+ * content, that existing content gets "finished".
+ * The public-facing methods of this class are FindPaintedLayerFor,
+ * AddingOwnLayer, and Finish. The other public methods are for
+ * PaintedLayerDataNode.
+ * The tree calls out to its containing ContainerState for some things.
+ * All coordinates / rects in the tree or the tree nodes are in the
+ * ContainerState's coordinate space, i.e. relative to the reference frame and
+ * in layer pixels.
+ * The clip rects of sibling nodes never overlap. This is ensured by finishing
+ * existing nodes before adding new ones, if this property were to be violated.
+ * The root tree node doesn't get finished until the ContainerState is
+ * finished.
+ * The tree's root node is always the root reference frame of the builder. We
+ * don't stop at the container state's mContainerAnimatedGeometryRoot because
+ * some of our contents can have animated geometry roots that are not
+ * descendants of the container's animated geometry root. Every animated
+ * geometry root we encounter for our contents needs to have a defined place in
+ * the tree.
+ */
+class PaintedLayerDataTree {
+public:
+ PaintedLayerDataTree(ContainerState& aContainerState,
+ nscolor& aBackgroundColor)
+ : mContainerState(aContainerState)
+ , mContainerUniformBackgroundColor(aBackgroundColor)
+ {}
+
+ ~PaintedLayerDataTree()
+ {
+ MOZ_ASSERT(!mRoot);
+ MOZ_ASSERT(mNodes.Count() == 0);
+ }
+
+ /**
+ * Notify our contents that some non-PaintedLayer content has been added.
+ * *aRect needs to be a rectangle that doesn't move with respect to
+ * aAnimatedGeometryRoot and that contains the added item.
+ * If aRect is null, the extents will be considered infinite.
+ * If aOutUniformBackgroundColor is non-null, it will be set to an opaque
+ * color that can be pulled into the background of the added content, or
+ * transparent if that is not possible.
+ */
+ void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIntRect* aRect,
+ nscolor* aOutUniformBackgroundColor);
+
+ /**
+ * Find a PaintedLayerData for aItem. This can either be an existing
+ * PaintedLayerData from inside a node in our tree, or a new one that gets
+ * created by a call out to aNewPaintedLayerCallback.
+ */
+ template<typename NewPaintedLayerCallbackType>
+ PaintedLayerData* FindPaintedLayerFor(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemScrollClip* aScrollClip,
+ const nsIntRect& aVisibleRect,
+ bool aBackfaceidden,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+ /**
+ * Finish everything.
+ */
+ void Finish();
+
+ /**
+ * Get the parent animated geometry root of aAnimatedGeometryRoot.
+ * That's either aAnimatedGeometryRoot's animated geometry root, or, if
+ * that's aAnimatedGeometryRoot itself, then it's the animated geometry
+ * root for aAnimatedGeometryRoot's cross-doc parent frame.
+ */
+ AnimatedGeometryRoot* GetParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with
+ * respect to aAnimatedGeometryRoot's parent animated geometry root.
+ * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's
+ * scroll port, otherwise there is no clip.
+ * This method doesn't have much to do with PaintedLayerDataTree, but this is
+ * where we have easy access to a display list builder, which we use to get
+ * the clip rect result into the right coordinate space.
+ */
+ bool IsClippedWithRespectToParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ nsIntRect* aOutClip);
+
+ /**
+ * Called by PaintedLayerDataNode when it is finished, so that we can drop
+ * our pointers to it.
+ */
+ void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ nsDisplayListBuilder* Builder() const;
+ ContainerState& ContState() const { return mContainerState; }
+ nscolor UniformBackgroundColor() const { return mContainerUniformBackgroundColor; }
+
+protected:
+ /**
+ * Finish all nodes that potentially intersect *aRect, where *aRect is a rect
+ * that doesn't move with respect to aAnimatedGeometryRoot.
+ * If aRect is null, *aRect will be considered infinite.
+ */
+ void FinishPotentiallyIntersectingNodes(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIntRect* aRect);
+
+ /**
+ * Make sure that there is a node for aAnimatedGeometryRoot and all of its
+ * ancestor geometry roots. Return the node for aAnimatedGeometryRoot.
+ */
+ PaintedLayerDataNode* EnsureNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot.
+ * *aOutAncestorChild will be set to the last ancestor that was encountered
+ * in the search up from aAnimatedGeometryRoot; it will be a child animated
+ * geometry root of the result, if neither are null.
+ */
+ PaintedLayerDataNode*
+ FindNodeForAncestorAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot** aOutAncestorChild);
+
+ ContainerState& mContainerState;
+ UniquePtr<PaintedLayerDataNode> mRoot;
+
+ /**
+ * The uniform opaque color from behind this container layer, or
+ * NS_RGBA(0,0,0,0) if the background behind this container layer is not
+ * uniform and opaque. This color can be pulled into PaintedLayers that are
+ * directly above the background.
+ */
+ nscolor mContainerUniformBackgroundColor;
+
+ /**
+ * A hash map for quick access the node belonging to a particular animated
+ * geometry root.
+ */
+ nsDataHashtable<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*> mNodes;
+};
+
+/**
+ * This is a helper object used to build up the layer children for
+ * a ContainerLayer.
+ */
+class ContainerState {
+public:
+ ContainerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ FrameLayerBuilder* aLayerBuilder,
+ nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ const nsRect& aContainerBounds,
+ ContainerLayer* aContainerLayer,
+ const ContainerLayerParameters& aParameters,
+ bool aFlattenToSingleLayer,
+ nscolor aBackgroundColor,
+ const DisplayItemScrollClip* aContainerScrollClip) :
+ mBuilder(aBuilder), mManager(aManager),
+ mLayerBuilder(aLayerBuilder),
+ mContainerFrame(aContainerFrame),
+ mContainerLayer(aContainerLayer),
+ mContainerBounds(aContainerBounds),
+ mContainerScrollClip(aContainerScrollClip),
+ mScrollClipForPerspectiveChild(aParameters.mScrollClipForPerspectiveChild),
+ mParameters(aParameters),
+ mPaintedLayerDataTree(*this, aBackgroundColor),
+ mFlattenToSingleLayer(aFlattenToSingleLayer)
+ {
+ nsPresContext* presContext = aContainerFrame->PresContext();
+ mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ mContainerReferenceFrame =
+ const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
+ mBuilder->FindReferenceFrameFor(mContainerFrame));
+ bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
+ MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame());
+ mContainerAnimatedGeometryRoot = isAtRoot
+ ? aBuilder->GetRootAnimatedGeometryRoot()
+ : aContainerItem->GetAnimatedGeometryRoot();
+ MOZ_ASSERT(!mBuilder->IsPaintingToWindow() ||
+ nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(),
+ *mContainerAnimatedGeometryRoot));
+ // When AllowResidualTranslation is false, display items will be drawn
+ // scaled with a translation by integer pixels, so we know how the snapping
+ // will work.
+ mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
+ !mParameters.AllowResidualTranslation();
+ CollectOldLayers();
+ }
+
+ /**
+ * This is the method that actually walks a display list and builds
+ * the child layers.
+ */
+ void ProcessDisplayItems(nsDisplayList* aList);
+ /**
+ * This finalizes all the open PaintedLayers by popping every element off
+ * mPaintedLayerDataStack, then sets the children of the container layer
+ * to be all the layers in mNewChildLayers in that order and removes any
+ * layers as children of the container that aren't in mNewChildLayers.
+ * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
+ * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
+ */
+ void Finish(uint32_t *aTextContentFlags,
+ const nsIntRect& aContainerPixelBounds,
+ nsDisplayList* aChildItems, bool* aHasComponentAlphaChildren);
+
+ nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }
+
+ nsIntRect ScaleToNearestPixels(const nsRect& aRect) const
+ {
+ return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const
+ {
+ return aRegion.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRect ScaleToOutsidePixels(const nsRect& aRect, bool aSnap = false) const
+ {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleToNearestPixels(aRect);
+ }
+ return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+ nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const
+ {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleToNearestPixels(aRect);
+ }
+ return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+
+ nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, bool aSnap = false) const
+ {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleRegionToNearestPixels(aRegion);
+ }
+ return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+
+ nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, bool aSnap = false) const
+ {
+ if (aSnap && mSnappingEnabled) {
+ return ScaleRegionToNearestPixels(aRegion);
+ }
+ return aRegion.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
+ mAppUnitsPerDevPixel);
+ }
+
+ nsIFrame* GetContainerFrame() const { return mContainerFrame; }
+ nsDisplayListBuilder* Builder() const { return mBuilder; }
+
+ /**
+ * Check if we are currently inside an inactive layer.
+ */
+ bool IsInInactiveLayer() const {
+ return mLayerBuilder->GetContainingPaintedLayerData();
+ }
+
+ /**
+ * Sets aOuterVisibleRegion as aLayer's visible region.
+ * @param aOuterVisibleRegion
+ * is in the coordinate space of the container reference frame.
+ * @param aLayerContentsVisibleRect, if non-null, is in the layer's own
+ * coordinate system.
+ * @param aOuterUntransformed is true if the given aOuterVisibleRegion
+ * is already untransformed with the matrix of the layer.
+ */
+ void SetOuterVisibleRegionForLayer(Layer* aLayer,
+ const nsIntRegion& aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect = nullptr,
+ bool aOuterUntransformed = false) const;
+
+ /**
+ * Try to determine whether the PaintedLayer aData has a single opaque color
+ * covering aRect. If successful, return that color, otherwise return
+ * NS_RGBA(0,0,0,0).
+ * If aRect turns out not to intersect any content in the layer,
+ * *aOutIntersectsLayer will be set to false.
+ */
+ nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
+ const nsIntRect& aRect,
+ bool* aOutIntersectsLayer) const;
+
+ /**
+ * Indicate that we are done adding items to the PaintedLayer represented by
+ * aData. Make sure that a real PaintedLayer exists for it, and set the final
+ * visible region and opaque-content.
+ */
+ template<typename FindOpaqueBackgroundColorCallbackType>
+ void FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor);
+
+protected:
+ friend class PaintedLayerData;
+
+ LayerManager::PaintedLayerCreationHint
+ GetLayerCreationHint(AnimatedGeometryRoot* aAnimatedGeometryRoot);
+
+ /**
+ * Creates a new PaintedLayer and sets up the transform on the PaintedLayer
+ * to account for scrolling.
+ */
+ already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData);
+
+ /**
+ * Find a PaintedLayer for recycling, recycle it and prepare it for use, or
+ * return null if no suitable layer was found.
+ */
+ already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ nsDisplayItem* aItem,
+ const nsPoint& aTopLeft);
+ /**
+ * Recycle aLayer and do any necessary invalidation.
+ */
+ PaintedDisplayItemLayerUserData* RecyclePaintedLayer(PaintedLayer* aLayer,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ bool& didResetScrollPositionForLayerPixelAlignment);
+
+ /**
+ * Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer:
+ * Initialize aData, set up the layer's transform for scrolling, and
+ * invalidate the layer for layer pixel alignment changes if necessary.
+ */
+ void PreparePaintedLayerForUse(PaintedLayer* aLayer,
+ PaintedDisplayItemLayerUserData* aData,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIFrame* aReferenceFrame,
+ const nsPoint& aTopLeft,
+ bool aDidResetScrollPositionForLayerPixelAlignment);
+
+ /**
+ * Attempt to prepare an ImageLayer based upon the provided PaintedLayerData.
+ * Returns nullptr on failure.
+ */
+ already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData);
+
+ /**
+ * Attempt to prepare a ColorLayer based upon the provided PaintedLayerData.
+ * Returns nullptr on failure.
+ */
+ already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData);
+
+ /**
+ * Grab the next recyclable ColorLayer, or create one if there are no
+ * more recyclable ColorLayers.
+ */
+ already_AddRefed<ColorLayer> CreateOrRecycleColorLayer(PaintedLayer* aPainted);
+ /**
+ * Grab the next recyclable ImageLayer, or create one if there are no
+ * more recyclable ImageLayers.
+ */
+ already_AddRefed<ImageLayer> CreateOrRecycleImageLayer(PaintedLayer* aPainted);
+ /**
+ * Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a
+ * mask layer which has been used for aLayer before), or create one if such
+ * a layer doesn't exist.
+ *
+ * Since mask layers can exist either on the layer directly, or as a side-
+ * attachment to FrameMetrics (for ancestor scrollframe clips), we key the
+ * recycle operation on both the originating layer and the mask layer's
+ * index in the layer, if any.
+ */
+ struct MaskLayerKey;
+ already_AddRefed<ImageLayer>
+ CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey,
+ mozilla::function<void(Layer* aLayer)> aSetUserData);
+ /**
+ * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes them
+ * available for recycling.
+ */
+ void CollectOldLayers();
+ /**
+ * If aItem used to belong to a PaintedLayer, invalidates the area of
+ * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area of
+ * aItem in that layer.
+ */
+ void InvalidateForLayerChange(nsDisplayItem* aItem,
+ PaintedLayer* aNewLayer);
+ /**
+ * Returns true if aItem's opaque area (in aOpaque) covers the entire
+ * scrollable area of its presshell.
+ */
+ bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque);
+
+ /**
+ * Set FrameMetrics and scroll-induced clipping on aEntry's layer.
+ */
+ void SetupScrollingMetadata(NewLayerEntry* aEntry);
+
+ /**
+ * Applies occlusion culling.
+ * For each layer in mNewChildLayers, remove from its visible region the
+ * opaque regions of the layers at higher z-index, but only if they have
+ * the same animated geometry root and fixed-pos frame ancestor.
+ * The opaque region for the child layers that share the same animated
+ * geometry root as the container frame is returned in
+ * *aOpaqueRegionForContainer.
+ *
+ * Also sets scroll metadata on the layers.
+ */
+ void PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer);
+
+ /**
+ * Computes the snapped opaque area of aItem. Sets aList's opaque flag
+ * if it covers the entire list bounds. Sets *aHideAllLayersBelow to true
+ * this item covers the entire viewport so that all layers below are
+ * permanently invisible.
+ */
+ nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemClip& aClip,
+ nsDisplayList* aList,
+ bool* aHideAllLayersBelow,
+ bool* aOpaqueForAnimatedGeometryRootParent);
+
+ /**
+ * Return a PaintedLayerData object that is initialized for a layer that
+ * aItem will be assigned to.
+ * @param aItem The item that is going to be added.
+ * @param aVisibleRect The visible rect of the item.
+ * @param aAnimatedGeometryRoot The item's animated geometry root.
+ * @param aScrollClip The scroll clip for this PaintedLayer.
+ * @param aTopLeft The offset between aAnimatedGeometryRoot and
+ * the reference frame.
+ * @param aShouldFixToViewport If true, aAnimatedGeometryRoot is the
+ * viewport and we will be adding fixed-pos
+ * metadata for this layer because the display
+ * item returned true from ShouldFixToViewport.
+ */
+ PaintedLayerData NewPaintedLayerData(nsDisplayItem* aItem,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemScrollClip* aScrollClip,
+ const nsPoint& aTopLeft,
+ bool aShouldFixToViewport);
+
+ /* Build a mask layer to represent the clipping region. Will return null if
+ * there is no clipping specified or a mask layer cannot be built.
+ * Builds an ImageLayer for the appropriate backend; the mask is relative to
+ * aLayer's visible region.
+ * aLayer is the layer to be clipped.
+ * relative to the container reference frame
+ * aRoundedRectClipCount is used when building mask layers for PaintedLayers,
+ * SetupMaskLayer will build a mask layer for only the first
+ * aRoundedRectClipCount rounded rects in aClip
+ */
+ void SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip,
+ uint32_t aRoundedRectClipCount = UINT32_MAX);
+
+ /**
+ * If |aClip| has rounded corners, create a mask layer for them, and
+ * add it to |aLayer|'s ancestor mask layers, returning an index into
+ * the array of ancestor mask layers. Returns an empty Maybe if
+ * |aClip| does not have rounded corners, or if no mask layer could
+ * be created.
+ */
+ Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer,
+ const DisplayItemClip& aClip);
+
+ /*
+ * Create/find a mask layer with suitable size for aMaskItem to paint
+ * css-positioned-masking onto.
+ */
+ void SetupMaskLayerForCSSMask(Layer* aLayer, nsDisplayMask* aMaskItem);
+
+ already_AddRefed<Layer> CreateMaskLayer(
+ Layer *aLayer, const DisplayItemClip& aClip,
+ const Maybe<size_t>& aForAncestorMaskLayer,
+ uint32_t aRoundedRectClipCount = UINT32_MAX);
+
+ bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
+ AnimatedGeometryRoot **aAnimatedGeometryRoot);
+
+ nsDisplayListBuilder* mBuilder;
+ LayerManager* mManager;
+ FrameLayerBuilder* mLayerBuilder;
+ nsIFrame* mContainerFrame;
+ nsIFrame* mContainerReferenceFrame;
+ AnimatedGeometryRoot* mContainerAnimatedGeometryRoot;
+ ContainerLayer* mContainerLayer;
+ nsRect mContainerBounds;
+ const DisplayItemScrollClip* mContainerScrollClip;
+ const DisplayItemScrollClip* mScrollClipForPerspectiveChild;
+#ifdef DEBUG
+ nsRect mAccumulatedChildBounds;
+#endif
+ ContainerLayerParameters mParameters;
+ /**
+ * The region of PaintedLayers that should be invalidated every time
+ * we recycle one.
+ */
+ nsIntRegion mInvalidPaintedContent;
+ PaintedLayerDataTree mPaintedLayerDataTree;
+ /**
+ * We collect the list of children in here. During ProcessDisplayItems,
+ * the layers in this array either have mContainerLayer as their parent,
+ * or no parent.
+ * PaintedLayers have two entries in this array: the second one is used only if
+ * the PaintedLayer is optimized away to a ColorLayer or ImageLayer.
+ * It's essential that this array is only appended to, since PaintedLayerData
+ * records the index of its PaintedLayer in this array.
+ */
+ typedef AutoTArray<NewLayerEntry,1> AutoLayersArray;
+ AutoLayersArray mNewChildLayers;
+ nsTHashtable<nsRefPtrHashKey<PaintedLayer>> mPaintedLayersAvailableForRecycling;
+ nscoord mAppUnitsPerDevPixel;
+ bool mSnappingEnabled;
+ bool mFlattenToSingleLayer;
+
+ struct MaskLayerKey {
+ MaskLayerKey() : mLayer(nullptr) {}
+ MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex)
+ : mLayer(aLayer),
+ mAncestorIndex(aAncestorIndex)
+ {}
+
+ PLDHashNumber Hash() const {
+ // Hash the layer and add the layer index to the hash.
+ return (NS_PTR_TO_UINT32(mLayer) >> 2)
+ + (mAncestorIndex ? (*mAncestorIndex + 1) : 0);
+ }
+ bool operator ==(const MaskLayerKey& aOther) const {
+ return mLayer == aOther.mLayer &&
+ mAncestorIndex == aOther.mAncestorIndex;
+ }
+
+ Layer* mLayer;
+ Maybe<size_t> mAncestorIndex;
+ };
+
+ nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>>
+ mRecycledMaskImageLayers;
+};
+
+class PaintedDisplayItemLayerUserData : public LayerUserData
+{
+public:
+ PaintedDisplayItemLayerUserData() :
+ mMaskClipCount(0),
+ mForcedBackgroundColor(NS_RGBA(0,0,0,0)),
+ mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)),
+ mXScale(1.f), mYScale(1.f),
+ mAppUnitsPerDevPixel(0),
+ mTranslation(0, 0),
+ mAnimatedGeometryRootPosition(0, 0) {}
+
+ /**
+ * Record the number of clips in the PaintedLayer's mask layer.
+ * Should not be reset when the layer is recycled since it is used to track
+ * changes in the use of mask layers.
+ */
+ uint32_t mMaskClipCount;
+
+ /**
+ * A color that should be painted over the bounds of the layer's visible
+ * region before any other content is painted.
+ */
+ nscolor mForcedBackgroundColor;
+
+ /**
+ * The target background color for smoothing fonts that are drawn on top of
+ * transparent parts of the layer.
+ */
+ nscolor mFontSmoothingBackgroundColor;
+
+ /**
+ * The resolution scale used.
+ */
+ float mXScale, mYScale;
+
+ /**
+ * The appunits per dev pixel for the items in this layer.
+ */
+ nscoord mAppUnitsPerDevPixel;
+
+ /**
+ * The offset from the PaintedLayer's 0,0 to the
+ * reference frame. This isn't necessarily the same as the transform
+ * set on the PaintedLayer since we might also be applying an extra
+ * offset specified by the parent ContainerLayer/
+ */
+ nsIntPoint mTranslation;
+
+ /**
+ * We try to make 0,0 of the PaintedLayer be the top-left of the
+ * border-box of the "active scrolled root" frame (i.e. the nearest ancestor
+ * frame for the display items that is being actively scrolled). But
+ * we force the PaintedLayer transform to be an integer translation, and we may
+ * have a resolution scale, so we have to snap the PaintedLayer transform, so
+ * 0,0 may not be exactly the top-left of the active scrolled root. Here we
+ * store the coordinates in PaintedLayer space of the top-left of the
+ * active scrolled root.
+ */
+ gfxPoint mAnimatedGeometryRootPosition;
+
+ nsIntRegion mRegionToInvalidate;
+
+ // The offset between the active scrolled root of this layer
+ // and the root of the container for the previous and current
+ // paints respectively.
+ nsPoint mLastAnimatedGeometryRootOrigin;
+ nsPoint mAnimatedGeometryRootOrigin;
+
+ RefPtr<ColorLayer> mColorLayer;
+ RefPtr<ImageLayer> mImageLayer;
+
+ // The region for which display item visibility for this layer has already
+ // been calculated. Used to reduce the number of calls to
+ // RecomputeVisibilityForItems if it is known in advance that a larger
+ // region will be painted during a transaction than in a single call to
+ // DrawPaintedLayer, for example when progressive paint is enabled.
+ nsIntRegion mVisibilityComputedRegion;
+};
+
+/*
+ * User data for layers which will be used as masks.
+ */
+struct MaskLayerUserData : public LayerUserData
+{
+ MaskLayerUserData()
+ : mScaleX(-1.0f)
+ , mScaleY(-1.0f)
+ , mAppUnitsPerDevPixel(-1)
+ { }
+ MaskLayerUserData(const DisplayItemClip& aClip,
+ uint32_t aRoundedRectClipCount,
+ int32_t aAppUnitsPerDevPixel,
+ const ContainerLayerParameters& aParams)
+ : mScaleX(aParams.mXScale)
+ , mScaleY(aParams.mYScale)
+ , mOffset(aParams.mOffset)
+ , mAppUnitsPerDevPixel(aAppUnitsPerDevPixel)
+ {
+ aClip.AppendRoundedRects(&mRoundedClipRects, aRoundedRectClipCount);
+ }
+
+ void operator=(MaskLayerUserData&& aOther)
+ {
+ mScaleX = aOther.mScaleX;
+ mScaleY = aOther.mScaleY;
+ mOffset = aOther.mOffset;
+ mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel;
+ mRoundedClipRects.SwapElements(aOther.mRoundedClipRects);
+ }
+
+ bool
+ operator== (const MaskLayerUserData& aOther) const
+ {
+ return mRoundedClipRects == aOther.mRoundedClipRects &&
+ mScaleX == aOther.mScaleX &&
+ mScaleY == aOther.mScaleY &&
+ mOffset == aOther.mOffset &&
+ mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel;
+ }
+
+ // Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var
+ MaskLayerImageCache::MaskLayerImageKeyRef mImageKey;
+ // properties of the mask layer; the mask layer may be re-used if these
+ // remain unchanged.
+ nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
+ // scale from the masked layer which is applied to the mask
+ float mScaleX, mScaleY;
+ // The ContainerLayerParameters offset which is applied to the mask's transform.
+ nsIntPoint mOffset;
+ int32_t mAppUnitsPerDevPixel;
+};
+
+/*
+ * User data for layers which will be used as masks for css positioned mask.
+ */
+struct CSSMaskLayerUserData : public LayerUserData
+{
+ CSSMaskLayerUserData()
+ : mImageLayers(nsStyleImageLayers::LayerType::Mask)
+ { }
+
+ CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aBounds)
+ : mImageLayers(aFrame->StyleSVGReset()->mMask),
+ mContentRect(aFrame->GetContentRectRelativeToSelf()),
+ mPaddingRect(aFrame->GetPaddingRectRelativeToSelf()),
+ mBorderRect(aFrame->GetRectRelativeToSelf()),
+ mMarginRect(aFrame->GetMarginRectRelativeToSelf()),
+ mBounds(aBounds)
+ {
+ Hash(aFrame);
+ }
+
+ CSSMaskLayerUserData& operator=(const CSSMaskLayerUserData& aOther)
+ {
+ mImageLayers = aOther.mImageLayers;
+
+ mContentRect = aOther.mContentRect;
+ mPaddingRect = aOther.mPaddingRect;
+ mBorderRect = aOther.mBorderRect;
+ mMarginRect = aOther.mMarginRect;
+
+ mBounds = aOther.mBounds;
+
+ mHash = aOther.mHash;
+
+ return *this;
+ }
+
+ bool
+ operator==(const CSSMaskLayerUserData& aOther) const
+ {
+ if (mHash != aOther.mHash) {
+ return false;
+ }
+
+ if (mImageLayers.mLayers != aOther.mImageLayers.mLayers) {
+ return false;
+ }
+
+ if (!mContentRect.IsEqualEdges(aOther.mContentRect) ||
+ !mPaddingRect.IsEqualEdges(aOther.mPaddingRect) ||
+ !mBorderRect.IsEqualEdges(aOther.mBorderRect) ||
+ !mMarginRect.IsEqualEdges(aOther.mMarginRect)) {
+ return false;
+ }
+
+ if (!mBounds.IsEqualEdges(aOther.mBounds)) {
+ return false;
+ }
+
+ return true;
+ }
+
+private:
+ void Hash(nsIFrame* aFrame)
+ {
+ uint32_t hash = 0;
+
+ const nsStyleImageLayers& imageLayers = aFrame->StyleSVGReset()->mMask;
+ for (uint32_t i = 0; i < imageLayers.mLayers.Length(); i++) {
+ const nsStyleImageLayers::Layer& newLayer = imageLayers.mLayers[i];
+ hash = AddToHash(hash, HashBytes(&newLayer, sizeof(newLayer)));
+ }
+
+ hash = AddToHash(hash, HashBytes(&mContentRect, sizeof(mContentRect)));
+ hash = AddToHash(hash, HashBytes(&mPaddingRect, sizeof(mPaddingRect)));
+ hash = AddToHash(hash, HashBytes(&mBorderRect, sizeof(mBorderRect)));
+ hash = AddToHash(hash, HashBytes(&mMarginRect, sizeof(mMarginRect)));
+
+ hash = AddToHash(hash, HashBytes(&mBounds, sizeof(mBounds)));
+
+ mHash = hash;
+ }
+
+ nsStyleImageLayers mImageLayers;
+
+ nsRect mContentRect;
+ nsRect mPaddingRect;
+ nsRect mBorderRect;
+ nsRect mMarginRect;
+
+ nsIntRect mBounds;
+
+ uint32_t mHash;
+};
+
+/*
+ * A helper object to create a draw target for painting mask and create a
+ * image container to hold the drawing result. The caller can then bind this
+ * image container with a image mask layer via ImageLayer::SetContainer.
+ */
+class MaskImageData
+{
+public:
+ MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
+ : mTextureClientLocked(false)
+ , mSize(aSize)
+ , mLayerManager(aLayerManager)
+ {
+ MOZ_ASSERT(!mSize.IsEmpty());
+ MOZ_ASSERT(mLayerManager);
+ }
+
+ ~MaskImageData()
+ {
+ if (mTextureClientLocked) {
+ MOZ_ASSERT(mTextureClient);
+ // Clear DrawTarget before Unlock.
+ mDrawTarget = nullptr;
+ mTextureClient->Unlock();
+ }
+ }
+
+ gfx::DrawTarget* CreateDrawTarget()
+ {
+ if (mDrawTarget) {
+ return mDrawTarget;
+ }
+
+ if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
+ return mDrawTarget;
+ }
+
+ MOZ_ASSERT(mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT);
+
+ ShadowLayerForwarder* fwd = mLayerManager->AsShadowForwarder();
+ if (!fwd) {
+ return nullptr;
+ }
+ mTextureClient =
+ TextureClient::CreateForDrawing(fwd,
+ SurfaceFormat::A8,
+ mSize,
+ BackendSelector::Content,
+ TextureFlags::DISALLOW_BIGIMAGE,
+ TextureAllocationFlags::ALLOC_CLEAR_BUFFER);
+ if (!mTextureClient) {
+ return nullptr;
+ }
+
+ mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
+ if (!mTextureClientLocked) {
+ return nullptr;
+ }
+
+ mDrawTarget = mTextureClient->BorrowDrawTarget();
+ return mDrawTarget;
+ }
+
+ already_AddRefed<ImageContainer> CreateImageAndImageContainer()
+ {
+ RefPtr<ImageContainer> container = mLayerManager->CreateImageContainer();
+ RefPtr<Image> image = CreateImage();
+
+ if (!image) {
+ return nullptr;
+ }
+ container->SetCurrentImageInTransaction(image);
+
+ return container.forget();
+ }
+
+private:
+ already_AddRefed<Image> CreateImage()
+ {
+ if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
+ mDrawTarget) {
+ RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
+ RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
+ // Disallow BIGIMAGE (splitting into multiple textures) for mask
+ // layer images
+ image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE);
+ return image.forget();
+ }
+
+ if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT &&
+ mTextureClient &&
+ mDrawTarget) {
+ RefPtr<TextureWrapperImage> image =
+ new TextureWrapperImage(mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
+ return image.forget();
+ }
+
+ return nullptr;
+ }
+
+ bool mTextureClientLocked;
+ gfx::IntSize mSize;
+ LayerManager* mLayerManager;
+ RefPtr<gfx::DrawTarget> mDrawTarget;
+ RefPtr<TextureClient> mTextureClient;
+};
+
+/**
+ * Helper functions for getting user data and casting it to the correct type.
+ * aLayer is the layer where the user data is stored.
+ */
+MaskLayerUserData* GetMaskLayerUserData(Layer* aLayer)
+{
+ return static_cast<MaskLayerUserData*>(aLayer->GetUserData(&gMaskLayerUserData));
+}
+
+PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(Layer* aLayer)
+{
+ return static_cast<PaintedDisplayItemLayerUserData*>(
+ aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+}
+
+/* static */ void
+FrameLayerBuilder::Shutdown()
+{
+ if (gMaskLayerImageCache) {
+ delete gMaskLayerImageCache;
+ gMaskLayerImageCache = nullptr;
+ }
+}
+
+void
+FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ PaintedLayerData* aLayerData)
+{
+ mDisplayListBuilder = aBuilder;
+ mRootPresContext = aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext();
+ if (mRootPresContext) {
+ mInitialDOMGeneration = mRootPresContext->GetDOMGeneration();
+ }
+ mContainingPaintedLayer = aLayerData;
+ aManager->SetUserData(&gLayerManagerLayerBuilder, this);
+}
+
+void
+FrameLayerBuilder::FlashPaint(gfxContext *aContext)
+{
+ float r = float(rand()) / RAND_MAX;
+ float g = float(rand()) / RAND_MAX;
+ float b = float(rand()) / RAND_MAX;
+ aContext->SetColor(Color(r, g, b, 0.4f));
+ aContext->Paint();
+}
+
+static FrameLayerBuilder::DisplayItemData*
+AssertDisplayItemData(FrameLayerBuilder::DisplayItemData* aData)
+{
+ MOZ_RELEASE_ASSERT(aData);
+ MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas && sAliveDisplayItemDatas->Contains(aData));
+ MOZ_RELEASE_ASSERT(aData->mLayer);
+ return aData;
+}
+
+FrameLayerBuilder::DisplayItemData*
+FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame, uint32_t aKey)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+ if (array) {
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ DisplayItemData* item = AssertDisplayItemData(array->ElementAt(i));
+ if (item->mDisplayItemKey == aKey &&
+ item->mLayer->Manager() == mRetainingManager) {
+ return item;
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsACString&
+AppendToString(nsACString& s, const nsIntRect& r,
+ const char* pfx="", const char* sfx="")
+{
+ s += pfx;
+ s += nsPrintfCString(
+ "(x=%d, y=%d, w=%d, h=%d)",
+ r.x, r.y, r.width, r.height);
+ return s += sfx;
+}
+
+nsACString&
+AppendToString(nsACString& s, const nsIntRegion& r,
+ const char* pfx="", const char* sfx="")
+{
+ s += pfx;
+
+ s += "< ";
+ for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
+ AppendToString(s, iter.Get()) += "; ";
+ }
+ s += ">";
+
+ return s += sfx;
+}
+
+/**
+ * Invalidate aRegion in aLayer. aLayer is in the coordinate system
+ * *after* aTranslation has been applied, so we need to
+ * apply the inverse of that transform before calling InvalidateRegion.
+ */
+static void
+InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsIntRegion& aRegion,
+ const nsIntPoint& aTranslation)
+{
+ // Convert the region from the coordinates of the container layer
+ // (relative to the snapped top-left of the display list reference frame)
+ // to the PaintedLayer's own coordinates
+ nsIntRegion rgn = aRegion;
+ rgn.MoveBy(-aTranslation);
+ aLayer->InvalidateRegion(rgn);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ nsAutoCString str;
+ AppendToString(str, rgn);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
+#endif
+}
+
+static void
+InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsRect& aRect,
+ const DisplayItemClip& aClip,
+ const nsIntPoint& aTranslation)
+{
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+
+ nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);
+
+ nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, data->mAppUnitsPerDevPixel);
+ InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
+}
+
+
+static nsIntPoint
+GetTranslationForPaintedLayer(PaintedLayer* aLayer)
+{
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>
+ (aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+ NS_ASSERTION(data, "Must be a tracked painted layer!");
+
+ return data->mTranslation;
+}
+
+/**
+ * Some frames can have multiple, nested, retaining layer managers
+ * associated with them (normal manager, inactive managers, SVG effects).
+ * In these cases we store the 'outermost' LayerManager data property
+ * on the frame since we can walk down the chain from there.
+ *
+ * If one of these frames has just been destroyed, we will free the inner
+ * layer manager when removing the entry from mFramesWithLayers. Destroying
+ * the layer manager destroys the LayerManagerData and calls into
+ * the DisplayItemData destructor. If the inner layer manager had any
+ * items with the same frame, then we attempt to retrieve properties
+ * from the deleted frame.
+ *
+ * Cache the destroyed frame pointer here so we can avoid crashing in this case.
+ */
+
+/* static */ void
+FrameLayerBuilder::RemoveFrameFromLayerManager(const nsIFrame* aFrame,
+ nsTArray<DisplayItemData*>* aArray)
+{
+ MOZ_RELEASE_ASSERT(!sDestroyedFrame);
+ sDestroyedFrame = aFrame;
+
+ // Hold a reference to all the items so that they don't get
+ // deleted from under us.
+ nsTArray<RefPtr<DisplayItemData> > arrayCopy;
+ for (DisplayItemData* data : *aArray) {
+ arrayCopy.AppendElement(data);
+ }
+
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ if (aArray->Length()) {
+ LayerManagerData *rootData = aArray->ElementAt(0)->mParent;
+ while (rootData->mParent) {
+ rootData = rootData->mParent;
+ }
+ printf_stderr("Removing frame %p - dumping display data\n", aFrame);
+ rootData->Dump();
+ }
+#endif
+
+ for (DisplayItemData* data : *aArray) {
+ PaintedLayer* t = data->mLayer->AsPaintedLayer();
+ if (t) {
+ PaintedDisplayItemLayerUserData* paintedData =
+ static_cast<PaintedDisplayItemLayerUserData*>(t->GetUserData(&gPaintedDisplayItemLayerUserData));
+ if (paintedData) {
+ nsRegion old = data->mGeometry->ComputeInvalidationRegion();
+ nsIntRegion rgn = old.ScaleToOutsidePixels(paintedData->mXScale, paintedData->mYScale, paintedData->mAppUnitsPerDevPixel);
+ rgn.MoveBy(-GetTranslationForPaintedLayer(t));
+ paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate, rgn);
+ paintedData->mRegionToInvalidate.SimplifyOutward(8);
+ }
+ }
+
+ data->mParent->mDisplayItems.RemoveEntry(data);
+ }
+
+ arrayCopy.Clear();
+ delete aArray;
+ sDestroyedFrame = nullptr;
+}
+
+void
+FrameLayerBuilder::DidBeginRetainedLayerTransaction(LayerManager* aManager)
+{
+ mRetainingManager = aManager;
+ LayerManagerData* data = static_cast<LayerManagerData*>
+ (aManager->GetUserData(&gLayerManagerUserData));
+ if (data) {
+ mInvalidateAllLayers = data->mInvalidateAllLayers;
+ } else {
+ data = new LayerManagerData(aManager);
+ aManager->SetUserData(&gLayerManagerUserData, data);
+ }
+}
+
+void
+FrameLayerBuilder::StoreOptimizedLayerForFrame(nsDisplayItem* aItem, Layer* aLayer)
+{
+ if (!mRetainingManager) {
+ return;
+ }
+
+ DisplayItemData* data = GetDisplayItemDataForManager(aItem, aLayer->Manager());
+ NS_ASSERTION(data, "Must have already stored data for this item!");
+ data->mOptLayer = aLayer;
+}
+
+void
+FrameLayerBuilder::DidEndTransaction()
+{
+ GetMaskLayerImageCache()->Sweep();
+}
+
+void
+FrameLayerBuilder::WillEndTransaction()
+{
+ if (!mRetainingManager) {
+ return;
+ }
+
+ // We need to save the data we'll need to support retaining.
+ LayerManagerData* data = static_cast<LayerManagerData*>
+ (mRetainingManager->GetUserData(&gLayerManagerUserData));
+ NS_ASSERTION(data, "Must have data!");
+
+ // Update all the frames that used to have layers.
+ for (auto iter = data->mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
+ DisplayItemData* data = iter.Get()->GetKey();
+ if (!data->mUsed) {
+ // This item was visible, but isn't anymore.
+ PaintedLayer* t = data->mLayer->AsPaintedLayer();
+ if (t && data->mGeometry) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating unused display item (%i) belonging to frame %p from layer %p\n", data->mDisplayItemKey, data->mFrameList[0], t);
+ }
+#endif
+ InvalidatePostTransformRegion(t,
+ data->mGeometry->ComputeInvalidationRegion(),
+ data->mClip,
+ GetLastPaintOffset(t));
+ }
+
+ data->ClearAnimationCompositorState();
+ iter.Remove();
+ } else {
+ ComputeGeometryChangeForItem(data);
+ }
+ }
+
+ data->mInvalidateAllLayers = false;
+}
+
+/* static */ FrameLayerBuilder::DisplayItemData*
+FrameLayerBuilder::GetDisplayItemDataForManager(nsDisplayItem* aItem,
+ LayerManager* aManager)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aItem->Frame()->Properties().Get(LayerManagerDataProperty());
+ if (array) {
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ DisplayItemData* item = AssertDisplayItemData(array->ElementAt(i));
+ if (item->mDisplayItemKey == aItem->GetPerFrameKey() &&
+ item->mLayer->Manager() == aManager) {
+ return item;
+ }
+ }
+ }
+ return nullptr;
+}
+
+bool
+FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+ if (array) {
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ if (AssertDisplayItemData(array->ElementAt(i))->mDisplayItemKey == aDisplayItemKey) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+FrameLayerBuilder::IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+ if (!array) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ DisplayItemData* data = AssertDisplayItemData(array->ElementAt(i));
+ if (data->mDisplayItemKey != nsDisplayItem::TYPE_ZERO) {
+ aCallback(aFrame, data);
+ }
+ }
+}
+
+FrameLayerBuilder::DisplayItemData*
+FrameLayerBuilder::GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey)
+{
+ // If we need to build a new layer tree, then just refuse to recycle
+ // anything.
+ if (!mRetainingManager || mInvalidateAllLayers)
+ return nullptr;
+
+ DisplayItemData *data = GetDisplayItemData(aFrame, aDisplayItemKey);
+
+ if (data && data->mLayer->Manager() == mRetainingManager) {
+ return data;
+ }
+ return nullptr;
+}
+
+Layer*
+FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem,
+ nsDisplayItemGeometry** aOldGeometry,
+ DisplayItemClip** aOldClip)
+{
+ uint32_t key = aItem->GetPerFrameKey();
+ nsIFrame* frame = aItem->Frame();
+
+ DisplayItemData* oldData = GetOldLayerForFrame(frame, key);
+ if (oldData) {
+ if (aOldGeometry) {
+ *aOldGeometry = oldData->mGeometry.get();
+ }
+ if (aOldClip) {
+ *aOldClip = &oldData->mClip;
+ }
+ return oldData->mLayer;
+ }
+
+ return nullptr;
+}
+
+void
+FrameLayerBuilder::ClearCachedGeometry(nsDisplayItem* aItem)
+{
+ uint32_t key = aItem->GetPerFrameKey();
+ nsIFrame* frame = aItem->Frame();
+
+ DisplayItemData* oldData = GetOldLayerForFrame(frame, key);
+ if (oldData) {
+ oldData->mGeometry = nullptr;
+ }
+}
+
+/* static */ Layer*
+FrameLayerBuilder::GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+
+ if (!array) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ DisplayItemData *data = AssertDisplayItemData(array->ElementAt(i));
+
+ if (data->mDisplayItemKey == aDisplayItemKey) {
+ return data->mLayer;
+ }
+ }
+ return nullptr;
+}
+
+/* static */ PaintedLayer*
+FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(nsIFrame* aFrame)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+
+ if (!array) {
+ return nullptr;
+ }
+
+ Layer* layer = nullptr;
+ for (DisplayItemData* data : *array) {
+ AssertDisplayItemData(data);
+ if (!data->mLayer->AsPaintedLayer()) {
+ continue;
+ }
+ if (layer && layer != data->mLayer) {
+ // More than one layer assigned, bail.
+ return nullptr;
+ }
+ layer = data->mLayer;
+ }
+
+ if (!layer) {
+ return nullptr;
+ }
+
+ return layer->AsPaintedLayer();
+}
+
+// Reset state that should not persist when a layer is recycled.
+static void
+ResetLayerStateForRecycling(Layer* aLayer) {
+ // Currently, this clears the mask layer and ancestor mask layers.
+ // Other cleanup may be added here.
+ aLayer->SetMaskLayer(nullptr);
+ aLayer->SetAncestorMaskLayers({});
+}
+
+already_AddRefed<ColorLayer>
+ContainerState::CreateOrRecycleColorLayer(PaintedLayer *aPainted)
+{
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>(aPainted->GetUserData(&gPaintedDisplayItemLayerUserData));
+ RefPtr<ColorLayer> layer = data->mColorLayer;
+ if (layer) {
+ ResetLayerStateForRecycling(layer);
+ layer->ClearExtraDumpInfo();
+ } else {
+ // Create a new layer
+ layer = mManager->CreateColorLayer();
+ if (!layer)
+ return nullptr;
+ // Mark this layer as being used for painting display items
+ data->mColorLayer = layer;
+ layer->SetUserData(&gColorLayerUserData, nullptr);
+
+ // Remove other layer types we might have stored for this PaintedLayer
+ data->mImageLayer = nullptr;
+ }
+ return layer.forget();
+}
+
+already_AddRefed<ImageLayer>
+ContainerState::CreateOrRecycleImageLayer(PaintedLayer *aPainted)
+{
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>(aPainted->GetUserData(&gPaintedDisplayItemLayerUserData));
+ RefPtr<ImageLayer> layer = data->mImageLayer;
+ if (layer) {
+ ResetLayerStateForRecycling(layer);
+ layer->ClearExtraDumpInfo();
+ } else {
+ // Create a new layer
+ layer = mManager->CreateImageLayer();
+ if (!layer)
+ return nullptr;
+ // Mark this layer as being used for painting display items
+ data->mImageLayer = layer;
+ layer->SetUserData(&gImageLayerUserData, nullptr);
+
+ // Remove other layer types we might have stored for this PaintedLayer
+ data->mColorLayer = nullptr;
+ }
+ return layer.forget();
+}
+
+already_AddRefed<ImageLayer>
+ContainerState::CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey,
+ mozilla::function<void(Layer* aLayer)> aSetUserData)
+{
+ RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey);
+ if (result) {
+ mRecycledMaskImageLayers.Remove(aKey);
+ aKey.mLayer->ClearExtraDumpInfo();
+ // XXX if we use clip on mask layers, null it out here
+ } else {
+ // Create a new layer
+ result = mManager->CreateImageLayer();
+ if (!result)
+ return nullptr;
+ aSetUserData(result);
+ }
+
+ return result.forget();
+}
+
+static const double SUBPIXEL_OFFSET_EPSILON = 0.02;
+
+/**
+ * This normally computes NSToIntRoundUp(aValue). However, if that would
+ * give a residual near 0.5 while aOldResidual is near -0.5, or
+ * it would give a residual near -0.5 while aOldResidual is near 0.5, then
+ * instead we return the integer in the other direction so that the residual
+ * is close to aOldResidual.
+ */
+static int32_t
+RoundToMatchResidual(double aValue, double aOldResidual)
+{
+ int32_t v = NSToIntRoundUp(aValue);
+ double residual = aValue - v;
+ if (aOldResidual < 0) {
+ if (residual > 0 && fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
+ // Round up instead
+ return int32_t(ceil(aValue));
+ }
+ } else if (aOldResidual > 0) {
+ if (residual < 0 && fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
+ // Round down instead
+ return int32_t(floor(aValue));
+ }
+ }
+ return v;
+}
+
+static void
+ResetScrollPositionForLayerPixelAlignment(AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (sf) {
+ sf->ResetScrollPositionForLayerPixelAlignment();
+ }
+}
+
+static void
+InvalidateEntirePaintedLayer(PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, const char *aReason)
+{
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason);
+ }
+#endif
+ nsIntRect invalidate = aLayer->GetValidRegion().GetBounds();
+ aLayer->InvalidateRegion(invalidate);
+ aLayer->SetInvalidRectToVisibleRegion();
+ ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot);
+}
+
+LayerManager::PaintedLayerCreationHint
+ContainerState::GetLayerCreationHint(AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ // Check whether the layer will be scrollable. This is used as a hint to
+ // influence whether tiled layers are used or not.
+
+ // Check creation hint inherited from our parent.
+ if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) {
+ return LayerManager::SCROLLABLE;
+ }
+
+ // Check whether there's any active scroll frame on the animated geometry
+ // root chain.
+ for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot;
+ agr && agr != mContainerAnimatedGeometryRoot;
+ agr = agr->mParentAGR) {
+ nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(*agr);
+ if (!fParent) {
+ break;
+ }
+ nsIScrollableFrame* scrollable = do_QueryFrame(fParent);
+ if (scrollable
+ #ifdef MOZ_B2G
+ && scrollable->WantAsyncScroll()
+ #endif
+ ) {
+ // WantAsyncScroll() returns false when the frame has overflow:hidden,
+ // so we won't create tiled layers for overflow:hidden frames even if
+ // they have a display port. The main purpose of the WantAsyncScroll check
+ // is to allow the B2G camera app to use hardware composer for compositing.
+ return LayerManager::SCROLLABLE;
+ }
+ }
+ return LayerManager::NONE;
+}
+
+already_AddRefed<PaintedLayer>
+ContainerState::AttemptToRecyclePaintedLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ nsDisplayItem* aItem,
+ const nsPoint& aTopLeft)
+{
+ Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem);
+ if (!oldLayer || !oldLayer->AsPaintedLayer() ||
+ !mPaintedLayersAvailableForRecycling.Contains(oldLayer->AsPaintedLayer())) {
+ return nullptr;
+ }
+
+ // Try to recycle a layer
+ RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer();
+ mPaintedLayersAvailableForRecycling.RemoveEntry(layer);
+
+ // Check if the layer hint has changed and whether or not the layer should
+ // be recreated because of it.
+ if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) {
+ return nullptr;
+ }
+
+ bool didResetScrollPositionForLayerPixelAlignment = false;
+ PaintedDisplayItemLayerUserData* data =
+ RecyclePaintedLayer(layer, aAnimatedGeometryRoot,
+ didResetScrollPositionForLayerPixelAlignment);
+ PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aItem->ReferenceFrame(),
+ aTopLeft,
+ didResetScrollPositionForLayerPixelAlignment);
+
+ return layer.forget();
+}
+
+already_AddRefed<PaintedLayer>
+ContainerState::CreatePaintedLayer(PaintedLayerData* aData)
+{
+ LayerManager::PaintedLayerCreationHint creationHint =
+ GetLayerCreationHint(aData->mAnimatedGeometryRoot);
+
+ // Create a new painted layer
+ RefPtr<PaintedLayer> layer = mManager->CreatePaintedLayerWithHint(creationHint);
+ if (!layer) {
+ return nullptr;
+ }
+
+ // Mark this layer as being used for painting display items
+ PaintedDisplayItemLayerUserData* userData = new PaintedDisplayItemLayerUserData();
+ layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData);
+ ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot);
+
+ PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot,
+ aData->mReferenceFrame,
+ aData->mAnimatedGeometryRootOffset, true);
+
+ return layer.forget();
+}
+
+PaintedDisplayItemLayerUserData*
+ContainerState::RecyclePaintedLayer(PaintedLayer* aLayer,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ bool& didResetScrollPositionForLayerPixelAlignment)
+{
+ // Clear clip rect and mask layer so we don't accidentally stay clipped.
+ // We will reapply any necessary clipping.
+ ResetLayerStateForRecycling(aLayer);
+ aLayer->ClearExtraDumpInfo();
+
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>(
+ aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+ NS_ASSERTION(data, "Recycled PaintedLayers must have user data");
+
+ // This gets called on recycled PaintedLayers that are going to be in the
+ // final layer tree, so it's a convenient time to invalidate the
+ // content that changed where we don't know what PaintedLayer it belonged
+ // to, or if we need to invalidate the entire layer, we can do that.
+ // This needs to be done before we update the PaintedLayer to its new
+ // transform. See nsGfxScrollFrame::InvalidateInternal, where
+ // we ensure that mInvalidPaintedContent is updated according to the
+ // scroll position as of the most recent paint.
+ if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) ||
+ !FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) ||
+ data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Recycled layer %p changed scale\n", aLayer);
+ }
+#endif
+ InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, "recycled layer changed state");
+ didResetScrollPositionForLayerPixelAlignment = true;
+ }
+ if (!data->mRegionToInvalidate.IsEmpty()) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating deleted frame content from layer %p\n", aLayer);
+ }
+#endif
+ aLayer->InvalidateRegion(data->mRegionToInvalidate);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ nsAutoCString str;
+ AppendToString(str, data->mRegionToInvalidate);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
+#endif
+ data->mRegionToInvalidate.SetEmpty();
+ }
+ return data;
+}
+
+void
+ContainerState::PreparePaintedLayerForUse(PaintedLayer* aLayer,
+ PaintedDisplayItemLayerUserData* aData,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIFrame* aReferenceFrame,
+ const nsPoint& aTopLeft,
+ bool didResetScrollPositionForLayerPixelAlignment)
+{
+ aData->mXScale = mParameters.mXScale;
+ aData->mYScale = mParameters.mYScale;
+ aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin;
+ aData->mAnimatedGeometryRootOrigin = aTopLeft;
+ aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
+ aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation());
+
+ mLayerBuilder->SavePreviousDataForLayer(aLayer, aData->mMaskClipCount);
+
+ // Set up transform so that 0,0 in the PaintedLayer corresponds to the
+ // (pixel-snapped) top-left of the aAnimatedGeometryRoot.
+ nsPoint offset = (*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame);
+ nscoord appUnitsPerDevPixel = (*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel();
+ gfxPoint scaledOffset(
+ NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel)*mParameters.mXScale,
+ NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel)*mParameters.mYScale);
+ // We call RoundToMatchResidual here so that the residual after rounding
+ // is close to aData->mAnimatedGeometryRootPosition if possible.
+ nsIntPoint pixOffset(RoundToMatchResidual(scaledOffset.x, aData->mAnimatedGeometryRootPosition.x),
+ RoundToMatchResidual(scaledOffset.y, aData->mAnimatedGeometryRootPosition.y));
+ aData->mTranslation = pixOffset;
+ pixOffset += mParameters.mOffset;
+ Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
+ aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));
+
+ aData->mVisibilityComputedRegion.SetEmpty();
+
+ // FIXME: Temporary workaround for bug 681192 and bug 724786.
+#ifndef MOZ_WIDGET_ANDROID
+ // Calculate exact position of the top-left of the active scrolled root.
+ // This might not be 0,0 due to the snapping in ScaleToNearestPixels.
+ gfxPoint animatedGeometryRootTopLeft = scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset;
+ // If it has changed, then we need to invalidate the entire layer since the
+ // pixels in the layer buffer have the content at a (subpixel) offset
+ // from what we need.
+ if (!animatedGeometryRootTopLeft.WithinEpsilonOf(aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) {
+ aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
+ InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, "subpixel offset");
+ } else if (didResetScrollPositionForLayerPixelAlignment) {
+ aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
+ }
+#else
+ Unused << didResetScrollPositionForLayerPixelAlignment;
+#endif
+}
+
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+/**
+ * Returns the appunits per dev pixel for the item's frame
+ */
+static int32_t
+AppUnitsPerDevPixel(nsDisplayItem* aItem)
+{
+ // The underlying frame for zoom items is the root frame of the subdocument.
+ // But zoom display items report their bounds etc using the parent document's
+ // APD because zoom items act as a conversion layer between the two different
+ // APDs.
+ if (aItem->GetType() == nsDisplayItem::TYPE_ZOOM) {
+ return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
+ }
+ return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+}
+#endif
+
+/**
+ * Set the visible region for aLayer.
+ * aOuterVisibleRegion is the visible region relative to the parent layer.
+ * aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's
+ * own coordinate system to which the layer's visible region is restricted.
+ * Consumes *aOuterVisibleRegion.
+ */
+static void
+SetOuterVisibleRegion(Layer* aLayer, nsIntRegion* aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect = nullptr,
+ bool aOuterUntransformed = false)
+{
+ Matrix4x4 transform = aLayer->GetTransform();
+ Matrix transform2D;
+ if (aOuterUntransformed) {
+ if (aLayerContentsVisibleRect) {
+ aOuterVisibleRegion->And(*aOuterVisibleRegion,
+ *aLayerContentsVisibleRect);
+ }
+ } else if (transform.Is2D(&transform2D) && !transform2D.HasNonIntegerTranslation()) {
+ aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32));
+ if (aLayerContentsVisibleRect) {
+ aOuterVisibleRegion->And(*aOuterVisibleRegion, *aLayerContentsVisibleRect);
+ }
+ } else {
+ nsIntRect outerRect = aOuterVisibleRegion->GetBounds();
+ // if 'transform' is not invertible, then nothing will be displayed
+ // for the layer, so it doesn't really matter what we do here
+ Rect outerVisible(outerRect.x, outerRect.y, outerRect.width, outerRect.height);
+ transform.Invert();
+
+ Rect layerContentsVisible(-float(INT32_MAX) / 2, -float(INT32_MAX) / 2,
+ float(INT32_MAX), float(INT32_MAX));
+ if (aLayerContentsVisibleRect) {
+ NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 &&
+ aLayerContentsVisibleRect->height >= 0,
+ "Bad layer contents rectangle");
+ // restrict to aLayerContentsVisibleRect before call GfxRectToIntRect,
+ // in case layerVisible is extremely large (as it can be when
+ // projecting through the inverse of a 3D transform)
+ layerContentsVisible = Rect(
+ aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y,
+ aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height);
+ }
+ gfxRect layerVisible = ThebesRect(transform.ProjectRectBounds(outerVisible, layerContentsVisible));
+ layerVisible.RoundOut();
+ nsIntRect visRect;
+ if (gfxUtils::GfxRectToIntRect(layerVisible, &visRect)) {
+ *aOuterVisibleRegion = visRect;
+ } else {
+ aOuterVisibleRegion->SetEmpty();
+ }
+ }
+
+ aLayer->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion));
+}
+
+void
+ContainerState::SetOuterVisibleRegionForLayer(Layer* aLayer,
+ const nsIntRegion& aOuterVisibleRegion,
+ const nsIntRect* aLayerContentsVisibleRect,
+ bool aOuterUntransformed) const
+{
+ nsIntRegion visRegion = aOuterVisibleRegion;
+ if (!aOuterUntransformed) {
+ visRegion.MoveBy(mParameters.mOffset);
+ }
+ SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect,
+ aOuterUntransformed);
+}
+
+nscolor
+ContainerState::FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
+ const nsIntRect& aRect,
+ bool* aOutIntersectsLayer) const
+{
+ *aOutIntersectsLayer = true;
+
+ // Scan the candidate's display items.
+ nsIntRect deviceRect = aRect;
+ nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel);
+ appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
+
+ for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
+ nsDisplayItem* item = assignedItem.mItem;
+ bool snap;
+ nsRect bounds = item->GetBounds(mBuilder, &snap);
+ if (snap && mSnappingEnabled) {
+ nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
+ if (!snappedBounds.Intersects(deviceRect))
+ continue;
+
+ if (!snappedBounds.Contains(deviceRect))
+ return NS_RGBA(0,0,0,0);
+
+ } else {
+ // The layer's visible rect is already (close enough to) pixel
+ // aligned, so no need to round out and in here.
+ if (!bounds.Intersects(appUnitRect))
+ continue;
+
+ if (!bounds.Contains(appUnitRect))
+ return NS_RGBA(0,0,0,0);
+ }
+
+ if (item->IsInvisibleInRect(appUnitRect)) {
+ continue;
+ }
+
+ if (assignedItem.mClip.IsRectAffectedByClip(deviceRect,
+ mParameters.mXScale,
+ mParameters.mYScale,
+ mAppUnitsPerDevPixel)) {
+ return NS_RGBA(0,0,0,0);
+ }
+
+ Maybe<nscolor> color = item->IsUniform(mBuilder);
+ if (color && NS_GET_A(*color) == 255)
+ return *color;
+
+ return NS_RGBA(0,0,0,0);
+ }
+
+ *aOutIntersectsLayer = false;
+ return NS_RGBA(0,0,0,0);
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColor(const nsIntRegion& aTargetVisibleRegion,
+ int32_t aUnderIndex) const
+{
+ if (aUnderIndex == ABOVE_TOP) {
+ aUnderIndex = mPaintedLayerDataStack.Length();
+ }
+ for (int32_t i = aUnderIndex - 1; i >= 0; --i) {
+ const PaintedLayerData* candidate = &mPaintedLayerDataStack[i];
+ if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) {
+ // Some non-PaintedLayer content between target and candidate; this is
+ // hopeless
+ return NS_RGBA(0,0,0,0);
+ }
+
+ if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) {
+ // The layer doesn't intersect our target, ignore it and move on
+ continue;
+ }
+
+ bool intersectsLayer = true;
+ nsIntRect rect = aTargetVisibleRegion.GetBounds();
+ nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer(
+ candidate, rect, &intersectsLayer);
+ if (!intersectsLayer) {
+ continue;
+ }
+ return color;
+ }
+ if (mAllDrawingAboveBackground ||
+ !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion).IsEmpty()) {
+ // Some non-PaintedLayer content is between this node's background and target.
+ return NS_RGBA(0,0,0,0);
+ }
+ return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything() const
+{
+ if (!mPaintedLayerDataStack.IsEmpty() ||
+ mAllDrawingAboveBackground ||
+ !mVisibleAboveBackgroundRegion.IsEmpty()) {
+ return NS_RGBA(0,0,0,0);
+ }
+ return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const
+{
+ if (mParent) {
+ if (mHasClip) {
+ // Check whether our parent node has uniform content behind our whole
+ // clip.
+ // There's one tricky case here: If our parent node is also a scrollable,
+ // and is currently scrolled in such a way that this inner one is
+ // clipped by it, then it's not really clear how we should determine
+ // whether we have a uniform background in the parent: There might be
+ // non-uniform content in the parts that our scroll port covers in the
+ // parent and that are currently outside the parent's clip.
+ // For now, we'll fail to pull a background color in that case.
+ return mParent->FindOpaqueBackgroundColor(mClipRect);
+ }
+ return mParent->FindOpaqueBackgroundColorCoveringEverything();
+ }
+ // We are the root.
+ return mTree.UniformBackgroundColor();
+}
+
+void
+PaintedLayerData::UpdateCommonClipCount(
+ const DisplayItemClip& aCurrentClip)
+{
+ if (mCommonClipCount >= 0) {
+ mCommonClipCount = mItemClip.GetCommonRoundedRectCount(aCurrentClip, mCommonClipCount);
+ } else {
+ // first item in the layer
+ mCommonClipCount = aCurrentClip.GetRoundedRectCount();
+ }
+}
+
+bool
+PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder)
+{
+ if (!mImage) {
+ return false;
+ }
+
+ return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder);
+}
+
+already_AddRefed<ImageContainer>
+PaintedLayerData::GetContainerForImageLayer(nsDisplayListBuilder* aBuilder)
+{
+ if (!mImage) {
+ return nullptr;
+ }
+
+ return mImage->GetContainer(mLayer->Manager(), aBuilder);
+}
+
+PaintedLayerDataNode::PaintedLayerDataNode(PaintedLayerDataTree& aTree,
+ PaintedLayerDataNode* aParent,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot)
+ : mTree(aTree)
+ , mParent(aParent)
+ , mAnimatedGeometryRoot(aAnimatedGeometryRoot)
+ , mAllDrawingAboveBackground(false)
+{
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot));
+ mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(mAnimatedGeometryRoot, &mClipRect);
+}
+
+PaintedLayerDataNode::~PaintedLayerDataNode()
+{
+ MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
+ MOZ_ASSERT(mChildren.IsEmpty());
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataNode::AddChildNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot);
+ UniquePtr<PaintedLayerDataNode> child =
+ MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot);
+ mChildren.AppendElement(Move(child));
+ return mChildren.LastElement().get();
+}
+
+template<typename NewPaintedLayerCallbackType>
+PaintedLayerData*
+PaintedLayerDataNode::FindPaintedLayerFor(const nsIntRect& aVisibleRect,
+ bool aBackfaceHidden,
+ const DisplayItemScrollClip* aScrollClip,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback)
+{
+ if (!mPaintedLayerDataStack.IsEmpty()) {
+ PaintedLayerData* lowestUsableLayer = nullptr;
+ for (auto& data : Reversed(mPaintedLayerDataStack)) {
+ if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) {
+ break;
+ }
+ if (data.mBackfaceHidden == aBackfaceHidden &&
+ data.mScrollClip == aScrollClip) {
+ lowestUsableLayer = &data;
+ }
+ nsIntRegion visibleRegion = data.mVisibleRegion;
+ // Also check whether the event-regions intersect the visible rect,
+ // unless we're in an inactive layer, in which case the event-regions
+ // will be hoisted out into their own layer.
+ // For performance reasons, we check the intersection with the bounds
+ // of the event-regions.
+ if (!mTree.ContState().IsInInactiveLayer() &&
+ (data.mScaledHitRegionBounds.Intersects(aVisibleRect) ||
+ data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) {
+ break;
+ }
+ if (visibleRegion.Intersects(aVisibleRect)) {
+ break;
+ }
+ }
+ if (lowestUsableLayer) {
+ return lowestUsableLayer;
+ }
+ }
+ return mPaintedLayerDataStack.AppendElement(aNewPaintedLayerCallback());
+}
+
+void
+PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect)
+{
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ if (mChildren[i]->Intersects(aRect)) {
+ mChildren[i]->Finish(true);
+ mChildren.RemoveElementAt(i);
+ }
+ }
+}
+
+void
+PaintedLayerDataNode::FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion)
+{
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion);
+ }
+ mChildren.Clear();
+}
+
+void
+PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion)
+{
+ // Skip "visible above region" maintenance, because this node is going away.
+ FinishAllChildren(false);
+
+ PopAllPaintedLayerData();
+
+ if (mParent && aParentNeedsAccurateVisibleAboveRegion) {
+ if (mHasClip) {
+ mParent->AddToVisibleAboveRegion(mClipRect);
+ } else {
+ mParent->SetAllDrawingAbove();
+ }
+ }
+ mTree.NodeWasFinished(mAnimatedGeometryRoot);
+}
+
+void
+PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect)
+{
+ nsIntRegion& visibleAboveRegion = mPaintedLayerDataStack.IsEmpty()
+ ? mVisibleAboveBackgroundRegion
+ : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion;
+ visibleAboveRegion.Or(visibleAboveRegion, aRect);
+ visibleAboveRegion.SimplifyOutward(8);
+}
+
+void
+PaintedLayerDataNode::SetAllDrawingAbove()
+{
+ PopAllPaintedLayerData();
+ mAllDrawingAboveBackground = true;
+ mVisibleAboveBackgroundRegion.SetEmpty();
+}
+
+void
+PaintedLayerDataNode::PopPaintedLayerData()
+{
+ MOZ_ASSERT(!mPaintedLayerDataStack.IsEmpty());
+ size_t lastIndex = mPaintedLayerDataStack.Length() - 1;
+ PaintedLayerData& data = mPaintedLayerDataStack[lastIndex];
+ mTree.ContState().FinishPaintedLayerData(data, [this, &data, lastIndex]() {
+ return this->FindOpaqueBackgroundColor(data.mVisibleRegion, lastIndex);
+ });
+ mPaintedLayerDataStack.RemoveElementAt(lastIndex);
+}
+
+void
+PaintedLayerDataNode::PopAllPaintedLayerData()
+{
+ while (!mPaintedLayerDataStack.IsEmpty()) {
+ PopPaintedLayerData();
+ }
+}
+
+nsDisplayListBuilder*
+PaintedLayerDataTree::Builder() const
+{
+ return mContainerState.Builder();
+}
+
+void
+PaintedLayerDataTree::Finish()
+{
+ if (mRoot) {
+ mRoot->Finish(false);
+ }
+ MOZ_ASSERT(mNodes.Count() == 0);
+ mRoot = nullptr;
+}
+
+void
+PaintedLayerDataTree::NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ mNodes.Remove(aAnimatedGeometryRoot);
+}
+
+void
+PaintedLayerDataTree::AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIntRect* aRect,
+ nscolor* aOutUniformBackgroundColor)
+{
+ FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect);
+ PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot);
+ if (aRect) {
+ if (aOutUniformBackgroundColor) {
+ *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect);
+ }
+ node->AddToVisibleAboveRegion(*aRect);
+ } else {
+ if (aOutUniformBackgroundColor) {
+ *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColorCoveringEverything();
+ }
+ node->SetAllDrawingAbove();
+ }
+}
+
+template<typename NewPaintedLayerCallbackType>
+PaintedLayerData*
+PaintedLayerDataTree::FindPaintedLayerFor(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemScrollClip* aScrollClip,
+ const nsIntRect& aVisibleRect,
+ bool aBackfaceHidden,
+ NewPaintedLayerCallbackType aNewPaintedLayerCallback)
+{
+ const nsIntRect* bounds = &aVisibleRect;
+ FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds);
+ PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot);
+
+ PaintedLayerData* data =
+ node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aScrollClip,
+ aNewPaintedLayerCallback);
+ return data;
+}
+
+void
+PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const nsIntRect* aRect)
+{
+ AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr;
+ PaintedLayerDataNode* ancestorNode =
+ FindNodeForAncestorAnimatedGeometryRoot(aAnimatedGeometryRoot,
+ &ancestorThatIsChildOfCommonAncestor);
+ if (!ancestorNode) {
+ // None of our ancestors are in the tree. This should only happen if this
+ // is the very first item we're looking at.
+ MOZ_ASSERT(!mRoot);
+ return;
+ }
+
+ if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) {
+ // aAnimatedGeometryRoot already has a node in the tree.
+ // This is the common case.
+ MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor);
+ if (aRect) {
+ ancestorNode->FinishChildrenIntersecting(*aRect);
+ } else {
+ ancestorNode->FinishAllChildren();
+ }
+ return;
+ }
+
+ // We have found an existing ancestor, but it's a proper ancestor of our
+ // animated geometry root.
+ // ancestorThatIsChildOfCommonAncestor is the last animated geometry root
+ // encountered on the way up from aAnimatedGeometryRoot to ancestorNode.
+ MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor);
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(*ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot));
+ MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR == ancestorNode->GetAnimatedGeometryRoot());
+
+ // ancestorThatIsChildOfCommonAncestor is not in the tree yet!
+ MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor));
+
+ // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we
+ // finish all intersecting siblings.
+ nsIntRect clip;
+ if (IsClippedWithRespectToParentAnimatedGeometryRoot(ancestorThatIsChildOfCommonAncestor, &clip)) {
+ ancestorNode->FinishChildrenIntersecting(clip);
+ } else {
+ ancestorNode->FinishAllChildren();
+ }
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataTree::EnsureNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ MOZ_ASSERT(aAnimatedGeometryRoot);
+ PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+ if (node) {
+ return node;
+ }
+
+ AnimatedGeometryRoot* parentAnimatedGeometryRoot = aAnimatedGeometryRoot->mParentAGR;
+ if (!parentAnimatedGeometryRoot) {
+ MOZ_ASSERT(!mRoot);
+ MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame());
+ mRoot = MakeUnique<PaintedLayerDataNode>(*this, nullptr, aAnimatedGeometryRoot);
+ node = mRoot.get();
+ } else {
+ PaintedLayerDataNode* parentNode = EnsureNodeFor(parentAnimatedGeometryRoot);
+ MOZ_ASSERT(parentNode);
+ node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot);
+ }
+ MOZ_ASSERT(node);
+ mNodes.Put(aAnimatedGeometryRoot, node);
+ return node;
+}
+
+bool
+PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ nsIntRect* aOutClip)
+{
+ nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (!scrollableFrame) {
+ return false;
+ }
+ nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
+ nsRect scrollPort = scrollableFrame->GetScrollPortRect() + Builder()->ToReferenceFrame(scrollFrame);
+ *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort);
+ return true;
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot** aOutAncestorChild)
+{
+ if (!aAnimatedGeometryRoot) {
+ return nullptr;
+ }
+ PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+ if (node) {
+ return node;
+ }
+ *aOutAncestorChild = aAnimatedGeometryRoot;
+ return FindNodeForAncestorAnimatedGeometryRoot(aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild);
+}
+
+static bool
+CanOptimizeAwayPaintedLayer(PaintedLayerData* aData,
+ FrameLayerBuilder* aLayerBuilder)
+{
+ if (!aLayerBuilder->IsBuildingRetainedLayers()) {
+ return false;
+ }
+
+ // If there's no painted layer with valid content in it that we can reuse,
+ // always create a color or image layer (and potentially throw away an
+ // existing completely invalid painted layer).
+ if (aData->mLayer->GetValidRegion().IsEmpty()) {
+ return true;
+ }
+
+ // There is an existing painted layer we can reuse. Throwing it away can make
+ // compositing cheaper (see bug 946952), but it might cause us to re-allocate
+ // the painted layer frequently due to an animation. So we only discard it if
+ // we're in tree compression mode, which is triggered at a low frequency.
+ return aLayerBuilder->CheckInLayerTreeCompressionMode();
+}
+
+#ifdef DEBUG
+static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray,
+ Layer* aLayer)
+{
+ for (uint32_t i = 0; i < aArray.Length(); ++i) {
+ if (aArray[i].mLayer == aLayer) {
+ return i;
+ }
+ }
+ return -1;
+}
+#endif
+
+already_AddRefed<Layer>
+ContainerState::PrepareImageLayer(PaintedLayerData* aData)
+{
+ RefPtr<ImageContainer> imageContainer =
+ aData->GetContainerForImageLayer(mBuilder);
+ if (!imageContainer) {
+ return nullptr;
+ }
+
+ RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer);
+ imageLayer->SetContainer(imageContainer);
+ aData->mImage->ConfigureLayer(imageLayer, mParameters);
+ imageLayer->SetPostScale(mParameters.mXScale,
+ mParameters.mYScale);
+
+ if (aData->mItemClip.HasClip()) {
+ ParentLayerIntRect clip =
+ ViewAs<ParentLayerPixel>(ScaleToNearestPixels(aData->mItemClip.GetClipRect()));
+ clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
+ imageLayer->SetClipRect(Some(clip));
+ } else {
+ imageLayer->SetClipRect(Nothing());
+ }
+
+ mLayerBuilder->StoreOptimizedLayerForFrame(aData->mImage, imageLayer);
+ FLB_LOG_PAINTED_LAYER_DECISION(aData,
+ " Selected image layer=%p\n", imageLayer.get());
+
+ return imageLayer.forget();
+}
+
+already_AddRefed<Layer>
+ContainerState::PrepareColorLayer(PaintedLayerData* aData)
+{
+ RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer);
+ colorLayer->SetColor(Color::FromABGR(aData->mSolidColor));
+
+ // Copy transform
+ colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform());
+ colorLayer->SetPostScale(aData->mLayer->GetPostXScale(),
+ aData->mLayer->GetPostYScale());
+
+ nsIntRect visibleRect = aData->mVisibleRegion.GetBounds();
+ visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer));
+ colorLayer->SetBounds(visibleRect);
+ colorLayer->SetClipRect(Nothing());
+
+ FLB_LOG_PAINTED_LAYER_DECISION(aData,
+ " Selected color layer=%p\n", colorLayer.get());
+
+ return colorLayer.forget();
+}
+
+static void
+SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer)
+{
+ if (aBackfaceHidden) {
+ aLayer->SetContentFlags(aLayer->GetContentFlags() |
+ Layer::CONTENT_BACKFACE_HIDDEN);
+ } else {
+ aLayer->SetContentFlags(aLayer->GetContentFlags() &
+ ~Layer::CONTENT_BACKFACE_HIDDEN);
+ }
+}
+
+template<typename FindOpaqueBackgroundColorCallbackType>
+void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor)
+{
+ PaintedLayerData* data = &aData;
+
+ if (!data->mLayer) {
+ // No layer was recycled, so we create a new one.
+ RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
+ data->mLayer = paintedLayer;
+
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
+ "Layer already in list???");
+ mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget();
+ }
+
+ for (auto& item : data->mAssignedDisplayItems) {
+ MOZ_ASSERT(item.mItem->GetType() != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS);
+
+ InvalidateForLayerChange(item.mItem, data->mLayer);
+ mLayerBuilder->AddPaintedDisplayItem(data, item.mItem, item.mClip,
+ *this, item.mLayerState,
+ data->mAnimatedGeometryRootOffset);
+ }
+
+ NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex];
+
+ RefPtr<Layer> layer;
+ bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder);
+
+ FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data);
+ FLB_LOG_PAINTED_LAYER_DECISION(data, " Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n",
+ data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n',
+ CanOptimizeAwayPaintedLayer(data, mLayerBuilder));
+
+ if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) &&
+ CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) {
+ NS_ASSERTION(!(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer),
+ "Can't be a solid color as well as an image!");
+
+ layer = canOptimizeToImageLayer ? PrepareImageLayer(data)
+ : PrepareColorLayer(data);
+
+ if (layer) {
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+ "Layer already in list???");
+ NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
+ "Painted layer at wrong index");
+ // Store optimized layer in reserved slot
+ newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
+ NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
+ newLayerEntry->mLayer = layer;
+ newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
+ newLayerEntry->mScrollClip = data->mScrollClip;
+
+ // Hide the PaintedLayer. We leave it in the layer tree so that we
+ // can find and recycle it later.
+ ParentLayerIntRect emptyRect;
+ data->mLayer->SetClipRect(Some(emptyRect));
+ data->mLayer->SetVisibleRegion(LayerIntRegion());
+ data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion().GetBounds());
+ data->mLayer->SetEventRegions(EventRegions());
+ }
+ }
+
+ if (!layer) {
+ // We couldn't optimize to an image layer or a color layer above.
+ layer = data->mLayer;
+ layer->SetClipRect(Nothing());
+ FLB_LOG_PAINTED_LAYER_DECISION(data, " Selected painted layer=%p\n", layer.get());
+ }
+
+ // If the layer is a fixed background layer, the clip on the fixed background
+ // display item was not applied to the opaque region in
+ // ContainerState::ComputeOpaqueRect(), but was saved in data->mItemClip.
+ // Apply it to the opaque region now. Note that it's important to do this
+ // before the opaque region is propagated to the NewLayerEntry below.
+ if (data->mSingleItemFixedToViewport && data->mItemClip.HasClip()) {
+ nsRect clipRect = data->mItemClip.GetClipRect();
+ nsRect insideRoundedCorners = data->mItemClip.ApproximateIntersectInward(clipRect);
+ nsIntRect insideRoundedCornersScaled = ScaleToInsidePixels(insideRoundedCorners);
+ data->mOpaqueRegion.AndWith(insideRoundedCornersScaled);
+ }
+
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ newLayerEntry->mVisibleRegion = data->mVisibleRegion;
+ newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
+ newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow;
+ newLayerEntry->mOpaqueForAnimatedGeometryRootParent = data->mOpaqueForAnimatedGeometryRootParent;
+ } else {
+ SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
+ }
+
+ nsIntRect layerBounds = data->mBounds;
+ layerBounds.MoveBy(-GetTranslationForPaintedLayer(data->mLayer));
+ layer->SetLayerBounds(layerBounds);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (!data->mLog.IsEmpty()) {
+ if (PaintedLayerData* containingPld = mLayerBuilder->GetContainingPaintedLayerData()) {
+ containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog));
+ } else {
+ layer->AddExtraDumpInfo(nsCString(data->mLog));
+ }
+ }
+#endif
+
+ nsIntRegion transparentRegion;
+ transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
+ bool isOpaque = transparentRegion.IsEmpty();
+ // For translucent PaintedLayers, try to find an opaque background
+ // color that covers the entire area beneath it so we can pull that
+ // color into this layer to make it opaque.
+ if (layer == data->mLayer) {
+ nscolor backgroundColor = NS_RGBA(0,0,0,0);
+ if (!isOpaque) {
+ backgroundColor = aFindOpaqueBackgroundColor();
+ if (NS_GET_A(backgroundColor) == 255) {
+ isOpaque = true;
+ }
+ }
+
+ // Store the background color
+ PaintedDisplayItemLayerUserData* userData =
+ GetPaintedDisplayItemLayerUserData(data->mLayer);
+ NS_ASSERTION(userData, "where did our user data go?");
+ if (userData->mForcedBackgroundColor != backgroundColor) {
+ // Invalidate the entire target PaintedLayer since we're changing
+ // the background color
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Forced background color has changed from #%08X to #%08X on layer %p\n",
+ userData->mForcedBackgroundColor, backgroundColor, data->mLayer);
+ nsAutoCString str;
+ AppendToString(str, data->mLayer->GetValidRegion());
+ printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get());
+ }
+#endif
+ data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion());
+ }
+ userData->mForcedBackgroundColor = backgroundColor;
+
+ userData->mFontSmoothingBackgroundColor = data->mFontSmoothingBackgroundColor;
+
+ // use a mask layer for rounded rect clipping.
+ // data->mCommonClipCount may be -1 if we haven't put any actual
+ // drawable items in this layer (i.e. it's only catching events).
+ int32_t commonClipCount;
+ // If the layer contains a single item fixed to the viewport, we removed
+ // its clip in ProcessDisplayItems() and saved it to set on the layer instead.
+ // Set the clip on the layer now.
+ if (data->mSingleItemFixedToViewport && data->mItemClip.HasClip()) {
+ nsIntRect layerClipRect = ScaleToNearestPixels(data->mItemClip.GetClipRect());
+ layerClipRect.MoveBy(mParameters.mOffset);
+ // The clip from such an item becomes part of the layer's scrolled clip,
+ // and the associated mask layer one of the layer's "ancestor mask layers".
+ LayerClip scrolledClip;
+ scrolledClip.SetClipRect(ViewAs<ParentLayerPixel>(layerClipRect));
+ scrolledClip.SetMaskLayerIndex(
+ SetupMaskLayerForScrolledClip(data->mLayer, data->mItemClip));
+ data->mLayer->SetScrolledClip(Some(scrolledClip));
+ // There is only one item, so all of the clips are in common to all items.
+ // data->mCommonClipCount will be zero because we removed the clip from
+ // the display item. (It could also be -1 if we're inside an inactive
+ // layer tree in which we don't call UpdateCommonClipCount() at all.)
+ MOZ_ASSERT(data->mCommonClipCount == -1 || data->mCommonClipCount == 0);
+ commonClipCount = data->mItemClip.GetRoundedRectCount();
+ } else {
+ commonClipCount = std::max(0, data->mCommonClipCount);
+ SetupMaskLayer(layer, data->mItemClip, commonClipCount);
+ }
+ // copy commonClipCount to the entry
+ FrameLayerBuilder::PaintedLayerItemsEntry* entry = mLayerBuilder->
+ GetPaintedLayerItemsEntry(static_cast<PaintedLayer*>(layer.get()));
+ entry->mCommonClipCount = commonClipCount;
+ } else {
+ // mask layer for image and color layers
+ SetupMaskLayer(layer, data->mItemClip);
+ }
+
+ uint32_t flags = 0;
+ nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget();
+ // See bug 941095. Not quite ready to disable this.
+ bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2;
+ if (hidpi) {
+ flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA;
+ }
+ if (isOpaque && !data->mForceTransparentSurface) {
+ flags |= Layer::CONTENT_OPAQUE;
+ } else if (data->mNeedComponentAlpha && !hidpi) {
+ flags |= Layer::CONTENT_COMPONENT_ALPHA;
+ }
+ if (data->mDisableFlattening) {
+ flags |= Layer::CONTENT_DISABLE_FLATTENING;
+ }
+ layer->SetContentFlags(flags);
+
+ PaintedLayerData* containingPaintedLayerData =
+ mLayerBuilder->GetContainingPaintedLayerData();
+ if (containingPaintedLayerData) {
+ if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
+ nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+ mContainerReferenceFrame,
+ data->mDispatchToContentHitRegion.GetBounds(),
+ containingPaintedLayerData->mReferenceFrame);
+ containingPaintedLayerData->mDispatchToContentHitRegion.Or(
+ containingPaintedLayerData->mDispatchToContentHitRegion, rect);
+ }
+ if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
+ nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+ mContainerReferenceFrame,
+ data->mMaybeHitRegion.GetBounds(),
+ containingPaintedLayerData->mReferenceFrame);
+ containingPaintedLayerData->mMaybeHitRegion.Or(
+ containingPaintedLayerData->mMaybeHitRegion, rect);
+ containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8);
+ }
+ Maybe<Matrix4x4> matrixCache;
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mHitRegion,
+ mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mHitRegion,
+ &containingPaintedLayerData->mMaybeHitRegion,
+ &matrixCache);
+ // See the comment in nsDisplayList::AddFrame, where the touch action regions
+ // are handled. The same thing applies here.
+ bool alreadyHadRegions =
+ !containingPaintedLayerData->mNoActionRegion.IsEmpty() ||
+ !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() ||
+ !containingPaintedLayerData->mVerticalPanRegion.IsEmpty();
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mNoActionRegion,
+ mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mNoActionRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion,
+ &matrixCache);
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mHorizontalPanRegion,
+ mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mHorizontalPanRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion,
+ &matrixCache);
+ nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ data->mVerticalPanRegion,
+ mContainerReferenceFrame,
+ containingPaintedLayerData->mReferenceFrame,
+ &containingPaintedLayerData->mVerticalPanRegion,
+ &containingPaintedLayerData->mDispatchToContentHitRegion,
+ &matrixCache);
+ if (alreadyHadRegions) {
+ containingPaintedLayerData->mDispatchToContentHitRegion.OrWith(
+ containingPaintedLayerData->CombinedTouchActionRegion());
+ }
+ } else {
+ EventRegions regions;
+ regions.mHitRegion = ScaleRegionToOutsidePixels(data->mHitRegion);
+ regions.mNoActionRegion = ScaleRegionToOutsidePixels(data->mNoActionRegion);
+ regions.mHorizontalPanRegion = ScaleRegionToOutsidePixels(data->mHorizontalPanRegion);
+ regions.mVerticalPanRegion = ScaleRegionToOutsidePixels(data->mVerticalPanRegion);
+ // Points whose hit-region status we're not sure about need to be dispatched
+ // to the content thread. If a point is in both maybeHitRegion and hitRegion
+ // then it's not a "maybe" any more, and doesn't go into the dispatch-to-
+ // content region.
+ nsIntRegion maybeHitRegion = ScaleRegionToOutsidePixels(data->mMaybeHitRegion);
+ regions.mDispatchToContentHitRegion.Sub(maybeHitRegion, regions.mHitRegion);
+ regions.mDispatchToContentHitRegion.OrWith(
+ ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion));
+ regions.mHitRegion.OrWith(maybeHitRegion);
+
+ Matrix mat = layer->GetTransform().As2D();
+ mat.Invert();
+ regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22);
+
+ layer->SetEventRegions(regions);
+ }
+
+ SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer);
+ if (layer != data->mLayer) {
+ SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer);
+ }
+}
+
+static bool
+IsItemAreaInWindowOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ const nsRect& aComponentAlphaBounds)
+{
+ if (!aItem->Frame()->PresContext()->IsChrome()) {
+ // Assume that Web content is always in the window opaque region.
+ return true;
+ }
+ if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) {
+ // aItem is probably in some transformed subtree.
+ // We're not going to bother figuring out where this landed, we're just
+ // going to assume it might have landed over a transparent part of
+ // the window.
+ return false;
+ }
+ return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds);
+}
+
+void
+PaintedLayerData::Accumulate(ContainerState* aState,
+ nsDisplayItem* aItem,
+ const nsIntRegion& aClippedOpaqueRegion,
+ const nsIntRect& aVisibleRect,
+ const DisplayItemClip& aClip,
+ LayerState aLayerState)
+{
+ FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(), aItem, aItem->Frame(), this);
+
+ bool snap;
+ nsRect itemBounds = aItem->GetBounds(aState->mBuilder, &snap);
+ mBounds = mBounds.Union(aState->ScaleToOutsidePixels(itemBounds, snap));
+
+ if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) {
+ mForceTransparentSurface = true;
+ }
+ if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
+ // Disable component alpha.
+ // Note that the transform (if any) on the PaintedLayer is always an integer translation so
+ // we don't have to factor that in here.
+ aItem->DisableComponentAlpha();
+ }
+
+ bool clipMatches = mItemClip == aClip;
+ mItemClip = aClip;
+
+ mAssignedDisplayItems.AppendElement(AssignedDisplayItem(aItem, aClip, aLayerState));
+
+ if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) &&
+ mVisibleRegion.Contains(aVisibleRect) && !mImage) {
+ // A very common case! Most pages have a PaintedLayer with the page
+ // background (opaque) visible and most or all of the page content over the
+ // top of that background.
+ // The rest of this method won't do anything. mVisibleRegion and mOpaqueRegion
+ // don't need updating. mVisibleRegion contains aVisibleRect already,
+ // mOpaqueRegion contains aVisibleRect and therefore whatever the opaque
+ // region of the item is. mVisibleRegion must contain mOpaqueRegion
+ // and therefore aVisibleRect.
+ return;
+ }
+
+ /* Mark as available for conversion to image layer if this is a nsDisplayImage and
+ * it's the only thing visible in this layer.
+ */
+ if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) &&
+ aClippedOpaqueRegion.Contains(mVisibleRegion) &&
+ aItem->SupportsOptimizingToImage()) {
+ mImage = static_cast<nsDisplayImageContainer*>(aItem);
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " Tracking image: nsDisplayImageContainer covers the layer\n");
+ } else if (mImage) {
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " No longer tracking image\n");
+ mImage = nullptr;
+ }
+
+ bool isFirstVisibleItem = mVisibleRegion.IsEmpty();
+ if (isFirstVisibleItem) {
+ nscolor fontSmoothingBGColor;
+ if (aItem->ProvidesFontSmoothingBackgroundColor(&fontSmoothingBGColor)) {
+ mFontSmoothingBackgroundColor = fontSmoothingBGColor;
+ }
+ }
+
+ Maybe<nscolor> uniformColor = aItem->IsUniform(aState->mBuilder);
+
+ // Some display items have to exist (so they can set forceTransparentSurface
+ // below) but don't draw anything. They'll return true for isUniform but
+ // a color with opacity 0.
+ if (!uniformColor || NS_GET_A(*uniformColor) > 0) {
+ // Make sure that the visible area is covered by uniform pixels. In
+ // particular this excludes cases where the edges of the item are not
+ // pixel-aligned (thus the item will not be truly uniform).
+ if (uniformColor) {
+ bool snap;
+ nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap);
+ if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) {
+ uniformColor = Nothing();
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " Display item does not cover the visible rect\n");
+ }
+ }
+ if (uniformColor) {
+ if (isFirstVisibleItem) {
+ // This color is all we have
+ mSolidColor = *uniformColor;
+ mIsSolidColorInVisibleRegion = true;
+ } else if (mIsSolidColorInVisibleRegion &&
+ mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) &&
+ clipMatches) {
+ // we can just blend the colors together
+ mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor);
+ } else {
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " Layer not a solid color: Can't blend colors togethers\n");
+ mIsSolidColorInVisibleRegion = false;
+ }
+ } else {
+ FLB_LOG_PAINTED_LAYER_DECISION(this, " Layer is not a solid color: Display item is not uniform over the visible bound\n");
+ mIsSolidColorInVisibleRegion = false;
+ }
+
+ mVisibleRegion.Or(mVisibleRegion, aVisibleRect);
+ mVisibleRegion.SimplifyOutward(4);
+ }
+
+ if (!aClippedOpaqueRegion.IsEmpty()) {
+ for (auto iter = aClippedOpaqueRegion.RectIter(); !iter.Done(); iter.Next()) {
+ // We don't use SimplifyInward here since it's not defined exactly
+ // what it will discard. For our purposes the most important case
+ // is a large opaque background at the bottom of z-order (e.g.,
+ // a canvas background), so we need to make sure that the first rect
+ // we see doesn't get discarded.
+ nsIntRegion tmp;
+ tmp.Or(mOpaqueRegion, iter.Get());
+ // Opaque display items in chrome documents whose window is partially
+ // transparent are always added to the opaque region. This helps ensure
+ // that we get as much subpixel-AA as possible in the chrome.
+ if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) {
+ mOpaqueRegion = Move(tmp);
+ }
+ }
+ }
+
+ if (!aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
+ nsRect componentAlpha = aItem->GetComponentAlphaBounds(aState->mBuilder);
+ if (!componentAlpha.IsEmpty()) {
+ nsIntRect componentAlphaRect =
+ aState->ScaleToOutsidePixels(componentAlpha, false).Intersect(aVisibleRect);
+ if (!mOpaqueRegion.Contains(componentAlphaRect)) {
+ if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem,
+ componentAlpha.Intersect(aItem->GetVisibleRect()))) {
+ mNeedComponentAlpha = true;
+ } else {
+ aItem->DisableComponentAlpha();
+ }
+ }
+ }
+ }
+
+ // Ensure animated text does not get flattened, even if it forces other
+ // content in the container to be layerized. The content backend might
+ // not support subpixel positioning of text that animated transforms can
+ // generate. bug 633097
+ if (aState->mParameters.mInActiveTransformedSubtree &&
+ (mNeedComponentAlpha ||
+ !aItem->GetComponentAlphaBounds(aState->mBuilder).IsEmpty())) {
+ mDisableFlattening = true;
+ }
+}
+
+nsRegion
+PaintedLayerData::CombinedTouchActionRegion()
+{
+ nsRegion result;
+ result.Or(mHorizontalPanRegion, mVerticalPanRegion);
+ result.OrWith(mNoActionRegion);
+ return result;
+}
+
+void
+PaintedLayerData::AccumulateEventRegions(ContainerState* aState, nsDisplayLayerEventRegions* aEventRegions)
+{
+ FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating event regions %p against pld=%p\n", aEventRegions, this);
+
+ mHitRegion.OrWith(aEventRegions->HitRegion());
+ mMaybeHitRegion.OrWith(aEventRegions->MaybeHitRegion());
+ mDispatchToContentHitRegion.OrWith(aEventRegions->DispatchToContentHitRegion());
+
+ // See the comment in nsDisplayList::AddFrame, where the touch action regions
+ // are handled. The same thing applies here.
+ bool alreadyHadRegions = !mNoActionRegion.IsEmpty() ||
+ !mHorizontalPanRegion.IsEmpty() ||
+ !mVerticalPanRegion.IsEmpty();
+ mNoActionRegion.OrWith(aEventRegions->NoActionRegion());
+ mHorizontalPanRegion.OrWith(aEventRegions->HorizontalPanRegion());
+ mVerticalPanRegion.OrWith(aEventRegions->VerticalPanRegion());
+ if (alreadyHadRegions) {
+ mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
+ }
+
+ // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
+ // for quick access in FindPaintedLayerFor().
+ mScaledHitRegionBounds = aState->ScaleToOutsidePixels(mHitRegion.GetBounds());
+ mScaledMaybeHitRegionBounds = aState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
+}
+
+PaintedLayerData
+ContainerState::NewPaintedLayerData(nsDisplayItem* aItem,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemScrollClip* aScrollClip,
+ const nsPoint& aTopLeft,
+ bool aShouldFixToViewport)
+{
+ PaintedLayerData data;
+ data.mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+ data.mScrollClip = aScrollClip;
+ data.mAnimatedGeometryRootOffset = aTopLeft;
+ data.mReferenceFrame = aItem->ReferenceFrame();
+ data.mSingleItemFixedToViewport = aShouldFixToViewport;
+ data.mBackfaceHidden = aItem->Frame()->In3DContextAndBackfaceIsHidden();
+
+ data.mNewChildLayersIndex = mNewChildLayers.Length();
+ NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+ newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+ newLayerEntry->mScrollClip = aScrollClip;
+ // newLayerEntry->mOpaqueRegion is filled in later from
+ // paintedLayerData->mOpaqueRegion, if necessary.
+
+ // Allocate another entry for this layer's optimization to ColorLayer/ImageLayer
+ mNewChildLayers.AppendElement();
+
+ return data;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void
+DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface)
+{
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"", string.BeginReading());
+ gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile);
+ fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n");
+}
+#endif
+
+static void
+PaintInactiveLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ nsDisplayItem* aItem,
+ gfxContext* aContext,
+ nsRenderingContext* aCtx)
+{
+ // This item has an inactive layer. Render it to a PaintedLayer
+ // using a temporary BasicLayerManager.
+ BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager);
+ RefPtr<gfxContext> context = aContext;
+#ifdef MOZ_DUMP_PAINTING
+ int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
+ nsIntRect itemVisibleRect =
+ aItem->GetVisibleRect().ToOutsidePixels(appUnitsPerDevPixel);
+
+ RefPtr<DrawTarget> tempDT;
+ if (gfxEnv::DumpPaint()) {
+ tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ itemVisibleRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (tempDT) {
+ context = gfxContext::CreateOrNull(tempDT);
+ if (!context) {
+ // Leave this as crash, it's in the debugging code, we want to know
+ gfxDevCrash(LogReason::InvalidContext) << "PaintInactive context problem " << gfx::hexa(tempDT);
+ return;
+ }
+ context->SetMatrix(gfxMatrix::Translation(-itemVisibleRect.x,
+ -itemVisibleRect.y));
+ }
+ }
+#endif
+ basic->BeginTransaction();
+ basic->SetTarget(context);
+
+ if (aItem->GetType() == nsDisplayItem::TYPE_MASK) {
+ static_cast<nsDisplayMask*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
+ if (basic->InTransaction()) {
+ basic->AbortTransaction();
+ }
+ } else if (aItem->GetType() == nsDisplayItem::TYPE_FILTER){
+ static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
+ if (basic->InTransaction()) {
+ basic->AbortTransaction();
+ }
+ } else {
+ basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
+ }
+ FrameLayerBuilder *builder = static_cast<FrameLayerBuilder*>(basic->GetUserData(&gLayerManagerLayerBuilder));
+ if (builder) {
+ builder->DidEndTransaction();
+ }
+
+ basic->SetTarget(nullptr);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaint() && tempDT) {
+ RefPtr<SourceSurface> surface = tempDT->Snapshot();
+ DumpPaintedImage(aItem, surface);
+
+ DrawTarget* drawTarget = aContext->GetDrawTarget();
+ Rect rect(itemVisibleRect.x, itemVisibleRect.y,
+ itemVisibleRect.width, itemVisibleRect.height);
+ drawTarget->DrawSurface(surface, rect, Rect(Point(0,0), rect.Size()));
+
+ aItem->SetPainted();
+ }
+#endif
+}
+
+/**
+ * Chooses a single active scrolled root for the entire display list, used
+ * when we are flattening layers.
+ */
+bool
+ContainerState::ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
+ AnimatedGeometryRoot **aAnimatedGeometryRoot)
+{
+ for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
+ LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
+ // Don't use an item that won't be part of any PaintedLayers to pick the
+ // active scrolled root.
+ if (layerState == LAYER_ACTIVE_FORCE) {
+ continue;
+ }
+
+ // Try using the actual active scrolled root of the backmost item, as that
+ // should result in the least invalidation when scrolling.
+ *aAnimatedGeometryRoot = item->GetAnimatedGeometryRoot();
+ return true;
+ }
+ return false;
+}
+
+nsIntRegion
+ContainerState::ComputeOpaqueRect(nsDisplayItem* aItem,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot,
+ const DisplayItemClip& aClip,
+ nsDisplayList* aList,
+ bool* aHideAllLayersBelow,
+ bool* aOpaqueForAnimatedGeometryRootParent)
+{
+ bool snapOpaque;
+ nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque);
+ if (opaque.IsEmpty()) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion opaquePixels;
+ nsRegion opaqueClipped;
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ opaqueClipped.Or(opaqueClipped,
+ aClip.ApproximateIntersectInward(iter.Get()));
+ }
+ if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot &&
+ opaqueClipped.Contains(mContainerBounds)) {
+ *aHideAllLayersBelow = true;
+ aList->SetIsOpaque();
+ }
+ // Add opaque areas to the "exclude glass" region. Only do this when our
+ // container layer is going to be the rootmost layer, otherwise transforms
+ // etc will mess us up (and opaque contributions from other containers are
+ // not needed).
+ if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) {
+ mBuilder->AddWindowOpaqueRegion(opaqueClipped);
+ }
+ opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
+
+ if (IsInInactiveLayer()) {
+ return opaquePixels;
+ }
+
+ nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
+ if (sf) {
+ nsRect displayport;
+ bool usingDisplayport =
+ nsLayoutUtils::GetDisplayPort((*aAnimatedGeometryRoot)->GetContent(), &displayport,
+ RelativeTo::ScrollFrame);
+ if (!usingDisplayport) {
+ // No async scrolling, so all that matters is that the layer contents
+ // cover the scrollport.
+ displayport = sf->GetScrollPortRect();
+ }
+ nsIFrame* scrollFrame = do_QueryFrame(sf);
+ displayport += scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame);
+ if (opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) {
+ *aOpaqueForAnimatedGeometryRootParent = true;
+ }
+ }
+ return opaquePixels;
+}
+
+static const DisplayItemScrollClip*
+InnermostScrollClipApplicableToAGR(const DisplayItemScrollClip* aItemScrollClip,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ // "Applicable" scroll clips are those that are for nsIScrollableFrames
+ // that are ancestors of aAnimatedGeometryRoot or ancestors of aContainerScrollClip.
+ // They can be applied to all items sharing this animated geometry root, so
+ // instead of applying to the items individually, they can be applied to the
+ // whole layer.
+ for (const DisplayItemScrollClip* scrollClip = aItemScrollClip;
+ scrollClip;
+ scrollClip = scrollClip->mParent) {
+ nsIFrame* scrolledFrame = scrollClip->mScrollableFrame->GetScrolledFrame();
+ if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame, *aAnimatedGeometryRoot)) {
+ // scrollClip and all its ancestors are applicable.
+ return scrollClip;
+ }
+ }
+ return nullptr;
+}
+
+Maybe<size_t>
+ContainerState::SetupMaskLayerForScrolledClip(Layer* aLayer,
+ const DisplayItemClip& aClip)
+{
+ if (aClip.GetRoundedRectCount() > 0) {
+ Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount());
+ if (RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, maskLayerIndex,
+ aClip.GetRoundedRectCount())) {
+ aLayer->AddAncestorMaskLayer(maskLayer);
+ return maskLayerIndex;
+ }
+ // Fall through to |return Nothing()|.
+ }
+ return Nothing();
+}
+
+void
+ContainerState::SetupMaskLayerForCSSMask(Layer* aLayer,
+ nsDisplayMask* aMaskItem)
+{
+ MOZ_ASSERT(mManager->IsCompositingCheap());
+
+ RefPtr<ImageLayer> maskLayer =
+ CreateOrRecycleMaskImageLayerFor(MaskLayerKey(aLayer, Nothing()),
+ [](Layer* aMaskLayer)
+ {
+ aMaskLayer->SetUserData(&gCSSMaskLayerUserData,
+ new CSSMaskLayerUserData());
+ }
+ );
+
+ CSSMaskLayerUserData* oldUserData =
+ static_cast<CSSMaskLayerUserData*>(maskLayer->GetUserData(&gCSSMaskLayerUserData));
+
+ bool snap;
+ nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
+ nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap);
+ CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect);
+ if (*oldUserData == newUserData) {
+ aLayer->SetMaskLayer(maskLayer);
+ return;
+ }
+
+ int32_t maxSize = mManager->GetMaxTextureSize();
+ IntSize surfaceSize(std::min(itemRect.width, maxSize),
+ std::min(itemRect.height, maxSize));
+
+ if (surfaceSize.IsEmpty()) {
+ // Return early if we know that the size of this mask surface is empty.
+ return;
+ }
+
+ MaskImageData imageData(surfaceSize, mManager);
+ RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
+ if (!dt || !dt->IsValid()) {
+ NS_WARNING("Could not create DrawTarget for mask layer.");
+ return;
+ }
+
+ RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
+ maskCtx->SetMatrix(gfxMatrix::Translation(-itemRect.TopLeft()));
+ maskCtx->Multiply(gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale));
+
+ if (!aMaskItem->PaintMask(mBuilder, maskCtx)) {
+ // Mostly because of mask resource is not ready.
+ return;
+ }
+
+ // Setup mask layer offset.
+ Matrix4x4 matrix;
+ matrix.PreTranslate(itemRect.x, itemRect.y, 0);
+ matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
+
+ maskLayer->SetBaseTransform(matrix);
+
+ RefPtr<ImageContainer> imgContainer =
+ imageData.CreateImageAndImageContainer();
+ if (!imgContainer) {
+ return;
+ }
+ maskLayer->SetContainer(imgContainer);
+
+ *oldUserData = newUserData;
+ aLayer->SetMaskLayer(maskLayer);
+}
+
+/*
+ * Iterate through the non-clip items in aList and its descendants.
+ * For each item we compute the effective clip rect. Each item is assigned
+ * to a layer. We invalidate the areas in PaintedLayers where an item
+ * has moved from one PaintedLayer to another. Also,
+ * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
+ * We set the clip rect for items that generated their own layer, and
+ * create a mask layer to do any rounded rect clipping.
+ * (PaintedLayers don't need a clip rect on the layer, we clip the items
+ * individually when we draw them.)
+ * We set the visible rect for all layers, although the actual setting
+ * of visible rects for some PaintedLayers is deferred until the calling
+ * of ContainerState::Finish.
+ */
+void
+ContainerState::ProcessDisplayItems(nsDisplayList* aList)
+{
+ PROFILER_LABEL("ContainerState", "ProcessDisplayItems",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ AnimatedGeometryRoot* lastAnimatedGeometryRoot = mContainerAnimatedGeometryRoot;
+ nsPoint lastAGRTopLeft;
+ nsPoint topLeft(0,0);
+
+ // When NO_COMPONENT_ALPHA is set, items will be flattened into a single
+ // layer, so we need to choose which active scrolled root to use for all
+ // items.
+ if (mFlattenToSingleLayer) {
+ if (ChooseAnimatedGeometryRoot(*aList, &lastAnimatedGeometryRoot)) {
+ lastAGRTopLeft = (*lastAnimatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
+ }
+ }
+
+ int32_t maxLayers = gfxPrefs::MaxActiveLayers();
+ int layerCount = 0;
+
+ nsDisplayList savedItems;
+ nsDisplayItem* item;
+ while ((item = aList->RemoveBottom()) != nullptr) {
+ nsDisplayItem::Type itemType = item->GetType();
+
+ // If the item is a event regions item, but is empty (has no regions in it)
+ // then we should just throw it out
+ if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+ nsDisplayLayerEventRegions* eventRegions =
+ static_cast<nsDisplayLayerEventRegions*>(item);
+ if (eventRegions->IsEmpty()) {
+ item->~nsDisplayItem();
+ continue;
+ }
+ }
+
+ // Peek ahead to the next item and try merging with it or swapping with it
+ // if necessary.
+ nsDisplayItem* aboveItem;
+ while ((aboveItem = aList->GetBottom()) != nullptr) {
+ if (aboveItem->TryMerge(item)) {
+ aList->RemoveBottom();
+ item->~nsDisplayItem();
+ item = aboveItem;
+ itemType = item->GetType();
+ } else {
+ break;
+ }
+ }
+
+ nsDisplayList* itemSameCoordinateSystemChildren
+ = item->GetSameCoordinateSystemChildren();
+ if (item->ShouldFlattenAway(mBuilder)) {
+ aList->AppendToBottom(itemSameCoordinateSystemChildren);
+ item->~nsDisplayItem();
+ continue;
+ }
+
+ savedItems.AppendToTop(item);
+
+ NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
+ "items in a container layer should all have the same app units per dev pixel");
+
+ if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
+ aList->SetNeedsTransparentSurface();
+ }
+
+ if (mParameters.mForEventsAndPluginsOnly && !item->GetChildren() &&
+ (itemType != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS &&
+ itemType != nsDisplayItem::TYPE_PLUGIN)) {
+ continue;
+ }
+
+ LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
+ if (layerState == LAYER_INACTIVE &&
+ nsDisplayItem::ForceActiveLayers()) {
+ layerState = LAYER_ACTIVE;
+ }
+
+ bool forceInactive;
+ AnimatedGeometryRoot* animatedGeometryRoot;
+ AnimatedGeometryRoot* animatedGeometryRootForClip = nullptr;
+ if (mFlattenToSingleLayer && layerState != LAYER_ACTIVE_FORCE) {
+ forceInactive = true;
+ animatedGeometryRoot = lastAnimatedGeometryRoot;
+ topLeft = lastAGRTopLeft;
+ } else {
+ forceInactive = false;
+ if (mManager->IsWidgetLayerManager()) {
+ animatedGeometryRoot = item->GetAnimatedGeometryRoot();
+ animatedGeometryRootForClip = item->AnimatedGeometryRootForScrollMetadata();
+ } else {
+ // For inactive layer subtrees, splitting content into PaintedLayers
+ // based on animated geometry roots is pointless. It's more efficient
+ // to build the minimum number of layers.
+ animatedGeometryRoot = mContainerAnimatedGeometryRoot;
+
+ }
+ topLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
+ }
+ if (!animatedGeometryRootForClip) {
+ animatedGeometryRootForClip = animatedGeometryRoot;
+ }
+
+ const DisplayItemScrollClip* itemScrollClip = item->ScrollClip();
+ // Now we need to separate the item's scroll clip chain into those scroll
+ // clips that can be applied to the whole layer (i.e. to all items
+ // sharing the item's animated geometry root), and those that need to be
+ // applied to the item itself.
+ const DisplayItemScrollClip* agrScrollClip =
+ InnermostScrollClipApplicableToAGR(itemScrollClip, animatedGeometryRootForClip);
+ MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(agrScrollClip, itemScrollClip));
+
+ if (agrScrollClip != itemScrollClip) {
+ // Pick up any scroll clips that should apply to the item and apply them.
+ DisplayItemClip clip = item->GetClip();
+ for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
+ scrollClip && scrollClip != agrScrollClip && scrollClip != mContainerScrollClip;
+ scrollClip = scrollClip->mParent) {
+ if (scrollClip->mClip) {
+ clip.IntersectWith(*scrollClip->mClip);
+ }
+ }
+ item->SetClip(mBuilder, clip);
+ }
+
+ bool clipMovesWithLayer = (animatedGeometryRoot == animatedGeometryRootForClip);
+
+ bool shouldFixToViewport = !clipMovesWithLayer &&
+ !(*animatedGeometryRoot)->GetParent() &&
+ item->ShouldFixToViewport(mBuilder);
+
+ // For items that are fixed to the viewport, remove their clip at the
+ // display item level because additional areas could be brought into
+ // view by async scrolling. Save the clip so we can set it on the layer
+ // instead later.
+ DisplayItemClip fixedToViewportClip = DisplayItemClip::NoClip();
+ if (shouldFixToViewport) {
+ fixedToViewportClip = item->GetClip();
+ item->SetClip(mBuilder, DisplayItemClip::NoClip());
+ }
+
+ bool snap;
+ nsRect itemContent = item->GetBounds(mBuilder, &snap);
+ if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+ nsDisplayLayerEventRegions* eventRegions =
+ static_cast<nsDisplayLayerEventRegions*>(item);
+ itemContent = eventRegions->GetHitRegionBounds(mBuilder, &snap);
+ }
+ nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap);
+ bool prerenderedTransform = itemType == nsDisplayItem::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder);
+ ParentLayerIntRect clipRect;
+ const DisplayItemClip& itemClip = item->GetClip();
+ if (itemClip.HasClip()) {
+ itemContent.IntersectRect(itemContent, itemClip.GetClipRect());
+ clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClip.GetClipRect()));
+ if (!prerenderedTransform) {
+ itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect());
+ }
+ clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
+ }
+#ifdef DEBUG
+ nsRect bounds = itemContent;
+ bool dummy;
+ if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+ bounds = item->GetBounds(mBuilder, &dummy);
+ if (itemClip.HasClip()) {
+ bounds.IntersectRect(bounds, itemClip.GetClipRect());
+ }
+ }
+ bounds = fixedToViewportClip.ApplyNonRoundedIntersection(bounds);
+ if (!bounds.IsEmpty()) {
+ for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
+ scrollClip && scrollClip != mContainerScrollClip;
+ scrollClip = scrollClip->mParent) {
+ if (scrollClip->mClip) {
+ if (scrollClip->mIsAsyncScrollable) {
+ bounds = scrollClip->mClip->GetClipRect();
+ } else {
+ bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds);
+ }
+ }
+ }
+ }
+ ((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, bounds);
+#endif
+
+ nsIntRect itemVisibleRect = itemDrawRect;
+ // We haven't computed visibility at this point, so item->GetVisibleRect()
+ // is just the dirty rect that item was initialized with. We intersect it
+ // with the clipped item bounds to get a tighter visible rect.
+ itemVisibleRect = itemVisibleRect.Intersect(
+ ScaleToOutsidePixels(item->GetVisibleRect(), false));
+
+ if (maxLayers != -1 && layerCount >= maxLayers) {
+ forceInactive = true;
+ }
+
+ // Assign the item to a layer
+ if (layerState == LAYER_ACTIVE_FORCE ||
+ (layerState == LAYER_INACTIVE && !mManager->IsWidgetLayerManager()) ||
+ (!forceInactive &&
+ (layerState == LAYER_ACTIVE_EMPTY ||
+ layerState == LAYER_ACTIVE))) {
+
+ layerCount++;
+
+ // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
+ // We should never see an empty layer with any visible content!
+ NS_ASSERTION(layerState != LAYER_ACTIVE_EMPTY ||
+ itemVisibleRect.IsEmpty(),
+ "State is LAYER_ACTIVE_EMPTY but visible rect is not.");
+
+ // As long as the new layer isn't going to be a PaintedLayer,
+ // InvalidateForLayerChange doesn't need the new layer pointer.
+ // We also need to check the old data now, because BuildLayer
+ // can overwrite it.
+ InvalidateForLayerChange(item, nullptr);
+
+ // If the item would have its own layer but is invisible, just hide it.
+ // Note that items without their own layers can't be skipped this
+ // way, since their PaintedLayer may decide it wants to draw them
+ // into its buffer even if they're currently covered.
+ if (itemVisibleRect.IsEmpty() &&
+ !item->ShouldBuildLayerEvenIfInvisible(mBuilder)) {
+ continue;
+ }
+
+ if (mScrollClipForPerspectiveChild) {
+ // We are the single transform child item of an nsDisplayPerspective.
+ // Our parent forwarded a scroll clip to us. Pick it up.
+ // We do this after any clipping has been applied, because this
+ // forwarded scroll clip is only used for scrolling (in the form of
+ // APZ frame metrics), not for clipping - the clip still belongs on
+ // the perspective item.
+ MOZ_ASSERT(itemType == nsDisplayItem::TYPE_TRANSFORM);
+ MOZ_ASSERT(!itemScrollClip);
+ MOZ_ASSERT(!agrScrollClip);
+ MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(mContainerScrollClip,
+ mScrollClipForPerspectiveChild));
+ itemScrollClip = mScrollClipForPerspectiveChild;
+ agrScrollClip = mScrollClipForPerspectiveChild;
+ }
+
+ // 3D-transformed layers don't necessarily draw in the order in which
+ // they're added to their parent container layer.
+ bool mayDrawOutOfOrder = itemType == nsDisplayItem::TYPE_TRANSFORM &&
+ (item->Frame()->Combines3DTransformWithAncestors() ||
+ item->Frame()->Extend3DContext());
+
+ // Let mPaintedLayerDataTree know about this item, so that
+ // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
+ // item, even though it's not in any PaintedLayerDataStack.
+ // Ideally we'd only need the "else" case here and have
+ // mPaintedLayerDataTree figure out the right clip from the animated
+ // geometry root that we give it, but it can't easily figure about
+ // overflow:hidden clips on ancestors just by looking at the frame.
+ // So we'll do a little hand holding and pass the clip instead of the
+ // visible rect for the two important cases.
+ nscolor uniformColor = NS_RGBA(0,0,0,0);
+ nscolor* uniformColorPtr = (mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr :
+ &uniformColor;
+ nsIntRect clipRectUntyped;
+ const DisplayItemClip& layerClip = shouldFixToViewport ? fixedToViewportClip : itemClip;
+ ParentLayerIntRect layerClipRect;
+ nsIntRect* clipPtr = nullptr;
+ if (layerClip.HasClip()) {
+ layerClipRect = ViewAs<ParentLayerPixel>(
+ ScaleToNearestPixels(layerClip.GetClipRect()) + mParameters.mOffset);
+ clipRectUntyped = layerClipRect.ToUnknownRect();
+ clipPtr = &clipRectUntyped;
+ }
+ if (*animatedGeometryRoot == item->Frame() &&
+ *animatedGeometryRoot != mBuilder->RootReferenceFrame()) {
+ // This is the case for scrollbar thumbs, for example. In that case the
+ // clip we care about is the overflow:hidden clip on the scrollbar.
+ mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot->mParentAGR,
+ clipPtr,
+ uniformColorPtr);
+ } else if (prerenderedTransform) {
+ mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
+ clipPtr,
+ uniformColorPtr);
+ } else if (shouldFixToViewport) {
+ mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRootForClip,
+ clipPtr,
+ uniformColorPtr);
+ } else {
+ // Using itemVisibleRect here isn't perfect. itemVisibleRect can be
+ // larger or smaller than the potential bounds of item's contents in
+ // animatedGeometryRoot: It's too large if there's a clipped display
+ // port somewhere among item's contents (see bug 1147673), and it can
+ // be too small if the contents can move, because it only looks at the
+ // contents' current bounds and doesn't anticipate any animations.
+ // Time will tell whether this is good enough, or whether we need to do
+ // something more sophisticated here.
+ mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
+ &itemVisibleRect, uniformColorPtr);
+ }
+
+ ContainerLayerParameters params = mParameters;
+ params.mBackgroundColor = uniformColor;
+ params.mLayerCreationHint = GetLayerCreationHint(animatedGeometryRoot);
+ params.mScrollClip = agrScrollClip;
+ params.mScrollClipForPerspectiveChild = nullptr;
+
+ if (itemType == nsDisplayItem::TYPE_PERSPECTIVE) {
+ // Perspective items have a single child item, an nsDisplayTransform.
+ // If the perspective item is scrolled, but the perspective-inducing
+ // frame is outside the scroll frame (indicated by this items AGR
+ // being outside that scroll frame), we have to take special care to
+ // make APZ scrolling work properly. APZ needs us to put the scroll
+ // frame's FrameMetrics on our child transform ContainerLayer instead.
+ // Our agrScrollClip is the scroll clip that's applicable to our
+ // perspective frame, so it won't be the scroll clip for the scrolled
+ // frame in the case that we care about, and we'll forward that scroll
+ // clip to our child.
+ params.mScrollClipForPerspectiveChild = itemScrollClip;
+ }
+
+ // Just use its layer.
+ // Set layerContentsVisibleRect.width/height to -1 to indicate we
+ // currently don't know. If BuildContainerLayerFor gets called by
+ // item->BuildLayer, this will be set to a proper rect.
+ nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
+ params.mLayerContentsVisibleRect = &layerContentsVisibleRect;
+ RefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, params);
+ if (!ownLayer) {
+ continue;
+ }
+
+ NS_ASSERTION(!ownLayer->AsPaintedLayer(),
+ "Should never have created a dedicated Painted layer!");
+
+ if (item->BackfaceIsHidden()) {
+ ownLayer->SetContentFlags(ownLayer->GetContentFlags() |
+ Layer::CONTENT_BACKFACE_HIDDEN);
+ } else {
+ ownLayer->SetContentFlags(ownLayer->GetContentFlags() &
+ ~Layer::CONTENT_BACKFACE_HIDDEN);
+ }
+
+ nsRect invalid;
+ if (item->IsInvalid(invalid)) {
+ ownLayer->SetInvalidRectToVisibleRegion();
+ }
+
+ // If it's not a ContainerLayer, we need to apply the scale transform
+ // ourselves.
+ if (!ownLayer->AsContainerLayer()) {
+ ownLayer->SetPostScale(mParameters.mXScale,
+ mParameters.mYScale);
+ }
+
+ // Update that layer's clip and visible rects.
+ NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager");
+ NS_ASSERTION(!ownLayer->HasUserData(&gLayerManagerUserData),
+ "We shouldn't have a FrameLayerBuilder-managed layer here!");
+ NS_ASSERTION(layerClip.HasClip() ||
+ layerClip.GetRoundedRectCount() == 0,
+ "If we have rounded rects, we must have a clip rect");
+
+ // It has its own layer. Update that layer's clip and visible rects.
+
+ ownLayer->SetClipRect(Nothing());
+ ownLayer->SetScrolledClip(Nothing());
+ if (layerClip.HasClip()) {
+ // For layers fixed to the viewport, the clip becomes part of the
+ // layer's scrolled clip. Otherwise, it becomes part of the layer clip.
+ if (shouldFixToViewport) {
+ LayerClip scrolledClip;
+ scrolledClip.SetClipRect(layerClipRect);
+ if (layerClip.GetRoundedRectCount() > 0) {
+ scrolledClip.SetMaskLayerIndex(
+ SetupMaskLayerForScrolledClip(ownLayer.get(), layerClip));
+ }
+ ownLayer->SetScrolledClip(Some(scrolledClip));
+ } else {
+ ownLayer->SetClipRect(Some(layerClipRect));
+
+ // rounded rectangle clipping using mask layers
+ // (must be done after visible rect is set on layer)
+ if (layerClip.GetRoundedRectCount() > 0) {
+ SetupMaskLayer(ownLayer, layerClip);
+ }
+ }
+ } else if (item->GetType() == nsDisplayItem::TYPE_MASK) {
+ nsDisplayMask* maskItem = static_cast<nsDisplayMask*>(item);
+ SetupMaskLayerForCSSMask(ownLayer, maskItem);
+ }
+
+ ContainerLayer* oldContainer = ownLayer->GetParent();
+ if (oldContainer && oldContainer != mContainerLayer) {
+ oldContainer->RemoveChild(ownLayer);
+ }
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
+ "Layer already in list???");
+
+ NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+ newLayerEntry->mLayer = ownLayer;
+ newLayerEntry->mAnimatedGeometryRoot = animatedGeometryRoot;
+ newLayerEntry->mScrollClip = agrScrollClip;
+ newLayerEntry->mLayerState = layerState;
+
+ // Don't attempt to flatten compnent alpha layers that are within
+ // a forced active layer, or an active transform;
+ if (itemType == nsDisplayItem::TYPE_TRANSFORM ||
+ layerState == LAYER_ACTIVE_FORCE) {
+ newLayerEntry->mPropagateComponentAlphaFlattening = false;
+ }
+ // nsDisplayTransform::BuildLayer must set layerContentsVisibleRect.
+ // We rely on this to ensure 3D transforms compute a reasonable
+ // layer visible region.
+ NS_ASSERTION(itemType != nsDisplayItem::TYPE_TRANSFORM ||
+ layerContentsVisibleRect.width >= 0,
+ "Transform items must set layerContentsVisibleRect!");
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
+ if (itemType == nsDisplayItem::TYPE_PERSPECTIVE ||
+ (itemType == nsDisplayItem::TYPE_TRANSFORM &&
+ (item->Frame()->Extend3DContext() ||
+ item->Frame()->Combines3DTransformWithAncestors() ||
+ item->Frame()->HasPerspective()))) {
+ // Give untransformed visible region as outer visible region
+ // to avoid failure caused by singular transforms.
+ newLayerEntry->mUntransformedVisibleRegion = true;
+ newLayerEntry->mVisibleRegion =
+ item->GetVisibleRectForChildren().ToOutsidePixels(mAppUnitsPerDevPixel);
+ } else {
+ newLayerEntry->mVisibleRegion = itemVisibleRect;
+ }
+ newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(item,
+ animatedGeometryRoot, layerClip, aList,
+ &newLayerEntry->mHideAllLayersBelow,
+ &newLayerEntry->mOpaqueForAnimatedGeometryRootParent);
+ } else {
+ bool useChildrenVisible =
+ itemType == nsDisplayItem::TYPE_TRANSFORM &&
+ (item->Frame()->IsPreserve3DLeaf() ||
+ item->Frame()->HasPerspective());
+ const nsIntRegion &visible = useChildrenVisible ?
+ item->GetVisibleRectForChildren().ToOutsidePixels(mAppUnitsPerDevPixel):
+ itemVisibleRect;
+
+ SetOuterVisibleRegionForLayer(ownLayer, visible,
+ layerContentsVisibleRect.width >= 0 ? &layerContentsVisibleRect : nullptr,
+ useChildrenVisible);
+ }
+ if (itemType == nsDisplayItem::TYPE_SCROLL_INFO_LAYER) {
+ nsDisplayScrollInfoLayer* scrollItem = static_cast<nsDisplayScrollInfoLayer*>(item);
+ newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false;
+ newLayerEntry->mBaseScrollMetadata =
+ scrollItem->ComputeScrollMetadata(ownLayer, mParameters);
+ } else if ((itemType == nsDisplayItem::TYPE_SUBDOCUMENT ||
+ itemType == nsDisplayItem::TYPE_ZOOM ||
+ itemType == nsDisplayItem::TYPE_RESOLUTION) &&
+ gfxPrefs::LayoutUseContainersForRootFrames())
+ {
+ newLayerEntry->mBaseScrollMetadata =
+ static_cast<nsDisplaySubDocument*>(item)->ComputeScrollMetadata(ownLayer, mParameters);
+ }
+
+ /**
+ * No need to allocate geometry for items that aren't
+ * part of a PaintedLayer.
+ */
+ mLayerBuilder->AddLayerDisplayItem(ownLayer, item, layerState, nullptr);
+ } else {
+ PaintedLayerData* paintedLayerData =
+ mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, agrScrollClip,
+ itemVisibleRect,
+ item->Frame()->In3DContextAndBackfaceIsHidden(),
+ [&]() {
+ return NewPaintedLayerData(item, animatedGeometryRoot, agrScrollClip,
+ topLeft, shouldFixToViewport);
+ });
+
+ if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+ nsDisplayLayerEventRegions* eventRegions =
+ static_cast<nsDisplayLayerEventRegions*>(item);
+ paintedLayerData->AccumulateEventRegions(this, eventRegions);
+ } else {
+ // check to see if the new item has rounded rect clips in common with
+ // other items in the layer
+ if (mManager->IsWidgetLayerManager()) {
+ paintedLayerData->UpdateCommonClipCount(itemClip);
+ }
+ nsIntRegion opaquePixels = ComputeOpaqueRect(item,
+ animatedGeometryRoot, itemClip, aList,
+ &paintedLayerData->mHideAllLayersBelow,
+ &paintedLayerData->mOpaqueForAnimatedGeometryRootParent);
+ MOZ_ASSERT(nsIntRegion(itemDrawRect).Contains(opaquePixels));
+ opaquePixels.AndWith(itemVisibleRect);
+ paintedLayerData->Accumulate(this, item, opaquePixels,
+ itemVisibleRect, itemClip, layerState);
+
+ // If we removed the clip from the display item above because it's
+ // fixed to the viewport, save it on the PaintedLayerData so we can
+ // set it on the layer later.
+ if (fixedToViewportClip.HasClip()) {
+ paintedLayerData->mItemClip = fixedToViewportClip;
+ }
+
+ if (!paintedLayerData->mLayer) {
+ // Try to recycle the old layer of this display item.
+ RefPtr<PaintedLayer> layer =
+ AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, topLeft);
+ if (layer) {
+ paintedLayerData->mLayer = layer;
+
+ NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+ "Layer already in list???");
+ mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer = layer.forget();
+ }
+ }
+ }
+ }
+
+ if (itemSameCoordinateSystemChildren &&
+ itemSameCoordinateSystemChildren->NeedsTransparentSurface()) {
+ aList->SetNeedsTransparentSurface();
+ }
+ }
+
+ aList->AppendToTop(&savedItems);
+}
+
+void
+ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer)
+{
+ NS_ASSERTION(aItem->GetPerFrameKey(),
+ "Display items that render using Thebes must have a key");
+ nsDisplayItemGeometry* oldGeometry = nullptr;
+ DisplayItemClip* oldClip = nullptr;
+ Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem, &oldGeometry, &oldClip);
+ if (aNewLayer != oldLayer && oldLayer) {
+ // The item has changed layers.
+ // Invalidate the old bounds in the old layer and new bounds in the new layer.
+ PaintedLayer* t = oldLayer->AsPaintedLayer();
+ if (t && oldGeometry) {
+ // Note that whenever the layer's scale changes, we invalidate the whole thing,
+ // so it doesn't matter whether we are using the old scale at last paint
+ // or a new scale here
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Display item type %s(%p) changed layers %p to %p!\n", aItem->Name(), aItem->Frame(), t, aNewLayer);
+ }
+#endif
+ InvalidatePostTransformRegion(t,
+ oldGeometry->ComputeInvalidationRegion(),
+ *oldClip,
+ mLayerBuilder->GetLastPaintOffset(t));
+ }
+ // Clear the old geometry so that invalidation thinks the item has been
+ // added this paint.
+ mLayerBuilder->ClearCachedGeometry(aItem);
+ aItem->NotifyRenderingChanged();
+ }
+}
+
+void
+FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData)
+{
+ nsDisplayItem *item = aData->mItem;
+ PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer();
+ if (!item || !paintedLayer) {
+ aData->EndUpdate();
+ return;
+ }
+
+ PaintedLayerItemsEntry* entry = mPaintedLayerItems.GetEntry(paintedLayer);
+
+ nsAutoPtr<nsDisplayItemGeometry> geometry;
+
+ PaintedDisplayItemLayerUserData* layerData =
+ static_cast<PaintedDisplayItemLayerUserData*>(aData->mLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+ nsPoint shift = layerData->mAnimatedGeometryRootOrigin - layerData->mLastAnimatedGeometryRootOrigin;
+
+ const DisplayItemClip& clip = item->GetClip();
+
+ // If the frame is marked as invalidated, and didn't specify a rect to invalidate then we want to
+ // invalidate both the old and new bounds, otherwise we only want to invalidate the changed areas.
+ // If we do get an invalid rect, then we want to add this on top of the change areas.
+ nsRect invalid;
+ nsRegion combined;
+ bool notifyRenderingChanged = true;
+ if (!aData->mGeometry) {
+ // This item is being added for the first time, invalidate its entire area.
+ geometry = item->AllocateGeometry(mDisplayListBuilder);
+ combined = clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion());
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Display item type %s(%p) added to layer %p!\n", item->Name(), item->Frame(), aData->mLayer.get());
+ }
+#endif
+ } else if (aData->mIsInvalid || (item->IsInvalid(invalid) && invalid.IsEmpty())) {
+ // Layout marked item/frame as needing repainting (without an explicit rect), invalidate the entire old and new areas.
+ geometry = item->AllocateGeometry(mDisplayListBuilder);
+ combined = aData->mClip.ApplyNonRoundedIntersection(aData->mGeometry->ComputeInvalidationRegion());
+ combined.MoveBy(shift);
+ combined.Or(combined, clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion()));
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Display item type %s(%p) (in layer %p) belongs to an invalidated frame!\n", item->Name(), item->Frame(), aData->mLayer.get());
+ }
+#endif
+ } else {
+ // Let the display item check for geometry changes and decide what needs to be
+ // repainted.
+
+ const nsTArray<nsIFrame*>& changedFrames = aData->GetFrameListChanges();
+ aData->mGeometry->MoveBy(shift);
+ item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry, &combined);
+
+ // We have an optimization to cache the drawing of background-attachment: fixed canvas
+ // background images so we can scroll and just blit them when they are flattened into
+ // the same layer as scrolling content. NotifyRenderingChanged is only used to tell
+ // the canvas bg image item to purge this cache. We want to be careful not to accidentally
+ // purge the cache if we are just invalidating due to scrolling (ie the background image
+ // moves on the scrolling layer but it's rendering stays the same) so if
+ // AddOffsetAndComputeDifference is the only thing that will invalidate we skip the
+ // NotifyRenderingChanged call (ComputeInvalidationRegion for background images also calls
+ // NotifyRenderingChanged if anything changes).
+ // Only allocate a new geometry object if something actually changed, otherwise the existing
+ // one should be fine. We always reallocate for inactive layers, since these types don't
+ // implement ComputeInvalidateRegion (and rely on the ComputeDifferences call in
+ // AddPaintedDisplayItem instead).
+ if (!combined.IsEmpty() || aData->mLayerState == LAYER_INACTIVE) {
+ geometry = item->AllocateGeometry(mDisplayListBuilder);
+ } else if (aData->mClip == clip && invalid.IsEmpty() && changedFrames.Length() == 0) {
+ notifyRenderingChanged = false;
+ }
+ aData->mClip.AddOffsetAndComputeDifference(entry->mCommonClipCount,
+ shift, aData->mGeometry->ComputeInvalidationRegion(),
+ clip, entry->mLastCommonClipCount,
+ geometry ? geometry->ComputeInvalidationRegion() :
+ aData->mGeometry->ComputeInvalidationRegion(),
+ &combined);
+
+ // Add in any rect that the frame specified
+ combined.Or(combined, invalid);
+
+ for (uint32_t i = 0; i < changedFrames.Length(); i++) {
+ combined.Or(combined, changedFrames[i]->GetVisualOverflowRect());
+ }
+
+ // Restrict invalidation to the clipped region
+ nsRegion clipRegion;
+ if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) {
+ combined.And(combined, clipRegion);
+ }
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ if (!combined.IsEmpty()) {
+ printf_stderr("Display item type %s(%p) (in layer %p) changed geometry!\n", item->Name(), item->Frame(), aData->mLayer.get());
+ }
+ }
+#endif
+ }
+ if (!combined.IsEmpty()) {
+ if (notifyRenderingChanged) {
+ item->NotifyRenderingChanged();
+ }
+ InvalidatePostTransformRegion(paintedLayer,
+ combined.ScaleToOutsidePixels(layerData->mXScale, layerData->mYScale, layerData->mAppUnitsPerDevPixel),
+ layerData->mTranslation);
+ }
+
+ aData->EndUpdate(geometry);
+}
+
+void
+FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
+ nsDisplayItem* aItem,
+ const DisplayItemClip& aClip,
+ ContainerState& aContainerState,
+ LayerState aLayerState,
+ const nsPoint& aTopLeft)
+{
+ PaintedLayer* layer = aLayerData->mLayer;
+ PaintedDisplayItemLayerUserData* paintedData =
+ static_cast<PaintedDisplayItemLayerUserData*>
+ (layer->GetUserData(&gPaintedDisplayItemLayerUserData));
+ RefPtr<BasicLayerManager> tempManager;
+ nsIntRect intClip;
+ bool hasClip = false;
+ if (aLayerState != LAYER_NONE) {
+ DisplayItemData *data = GetDisplayItemDataForManager(aItem, layer->Manager());
+ if (data) {
+ tempManager = data->mInactiveManager;
+ }
+ if (!tempManager) {
+ tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
+ }
+
+ // We need to grab these before calling AddLayerDisplayItem because it will overwrite them.
+ nsRegion clip;
+ DisplayItemClip* oldClip = nullptr;
+ GetOldLayerFor(aItem, nullptr, &oldClip);
+ hasClip = aClip.ComputeRegionInClips(oldClip,
+ aTopLeft - paintedData->mLastAnimatedGeometryRootOrigin,
+ &clip);
+
+ if (hasClip) {
+ intClip = clip.GetBounds().ScaleToOutsidePixels(paintedData->mXScale,
+ paintedData->mYScale,
+ paintedData->mAppUnitsPerDevPixel);
+ }
+ }
+
+ AddLayerDisplayItem(layer, aItem, aLayerState, tempManager);
+
+ PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(layer);
+ if (entry) {
+ entry->mContainerLayerFrame = aContainerState.GetContainerFrame();
+ if (entry->mContainerLayerGeneration == 0) {
+ entry->mContainerLayerGeneration = mContainerLayerGeneration;
+ }
+ if (tempManager) {
+ FLB_LOG_PAINTED_LAYER_DECISION(aLayerData, "Creating nested FLB for item %p\n", aItem);
+ FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
+ layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData);
+
+ tempManager->BeginTransaction();
+ if (mRetainingManager) {
+ layerBuilder->DidBeginRetainedLayerTransaction(tempManager);
+ }
+
+ UniquePtr<LayerProperties> props(LayerProperties::CloneFrom(tempManager->GetRoot()));
+ RefPtr<Layer> tmpLayer =
+ aItem->BuildLayer(mDisplayListBuilder, tempManager, ContainerLayerParameters());
+ // We have no easy way of detecting if this transaction will ever actually get finished.
+ // For now, I've just silenced the warning with nested transactions in BasicLayers.cpp
+ if (!tmpLayer) {
+ tempManager->EndTransaction(nullptr, nullptr);
+ tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ return;
+ }
+
+ bool snap;
+ nsRect visibleRect =
+ aItem->GetVisibleRect().Intersect(aItem->GetBounds(mDisplayListBuilder, &snap));
+ nsIntRegion rgn = visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
+ SetOuterVisibleRegion(tmpLayer, &rgn);
+
+ // If BuildLayer didn't call BuildContainerLayerFor, then our new layer won't have been
+ // stored in layerBuilder. Manually add it now.
+ if (mRetainingManager) {
+#ifdef DEBUG_DISPLAY_ITEM_DATA
+ LayerManagerData* parentLmd = static_cast<LayerManagerData*>
+ (layer->Manager()->GetUserData(&gLayerManagerUserData));
+ LayerManagerData* lmd = static_cast<LayerManagerData*>
+ (tempManager->GetUserData(&gLayerManagerUserData));
+ lmd->mParent = parentLmd;
+#endif
+ layerBuilder->StoreDataForFrame(aItem, tmpLayer, LAYER_ACTIVE);
+ }
+
+ tempManager->SetRoot(tmpLayer);
+ layerBuilder->WillEndTransaction();
+ tempManager->AbortTransaction();
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) {
+ fprintf_stderr(gfxUtils::sDumpPaintFile, "Basic layer tree for painting contents of display item %s(%p):\n", aItem->Name(), aItem->Frame());
+ std::stringstream stream;
+ tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
+ fprint_stderr(gfxUtils::sDumpPaintFile, stream); // not a typo, fprint_stderr declared in LayersLogging.h
+ }
+#endif
+
+ nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
+ props->MoveBy(-offset);
+ // Effective transforms are needed by ComputeDifferences().
+ tmpLayer->ComputeEffectiveTransforms(Matrix4x4());
+ nsIntRegion invalid = props->ComputeDifferences(tmpLayer, nullptr);
+ if (aLayerState == LAYER_SVG_EFFECTS) {
+ invalid = nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(aItem->Frame(),
+ aItem->ToReferenceFrame(),
+ invalid);
+ }
+ if (!invalid.IsEmpty()) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Inactive LayerManager(%p) for display item %s(%p) has an invalid region - invalidating layer %p\n", tempManager.get(), aItem->Name(), aItem->Frame(), layer);
+ }
+#endif
+ invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
+
+ if (hasClip) {
+ invalid.And(invalid, intClip);
+ }
+
+ InvalidatePostTransformRegion(layer, invalid,
+ GetTranslationForPaintedLayer(layer));
+ }
+ }
+ ClippedDisplayItem* cdi =
+ entry->mItems.AppendElement(ClippedDisplayItem(aItem,
+ mContainerLayerGeneration));
+ cdi->mInactiveLayerManager = tempManager;
+ }
+}
+
+FrameLayerBuilder::DisplayItemData*
+FrameLayerBuilder::StoreDataForFrame(nsDisplayItem* aItem, Layer* aLayer, LayerState aState)
+{
+ DisplayItemData* oldData = GetDisplayItemDataForManager(aItem, mRetainingManager);
+ if (oldData) {
+ if (!oldData->mUsed) {
+ oldData->BeginUpdate(aLayer, aState, mContainerLayerGeneration, aItem);
+ }
+ return oldData;
+ }
+
+ LayerManagerData* lmd = static_cast<LayerManagerData*>
+ (mRetainingManager->GetUserData(&gLayerManagerUserData));
+
+ RefPtr<DisplayItemData> data =
+ new DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer);
+
+ data->BeginUpdate(aLayer, aState, mContainerLayerGeneration, aItem);
+
+ lmd->mDisplayItems.PutEntry(data);
+ return data;
+}
+
+void
+FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey,
+ Layer* aLayer,
+ LayerState aState)
+{
+ DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey);
+ if (oldData && oldData->mFrameList.Length() == 1) {
+ oldData->BeginUpdate(aLayer, aState, mContainerLayerGeneration);
+ return;
+ }
+
+ LayerManagerData* lmd = static_cast<LayerManagerData*>
+ (mRetainingManager->GetUserData(&gLayerManagerUserData));
+
+ RefPtr<DisplayItemData> data =
+ new DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame);
+
+ data->BeginUpdate(aLayer, aState, mContainerLayerGeneration);
+
+ lmd->mDisplayItems.PutEntry(data);
+}
+
+FrameLayerBuilder::ClippedDisplayItem::ClippedDisplayItem(nsDisplayItem* aItem,
+ uint32_t aGeneration)
+ : mItem(aItem)
+ , mContainerLayerGeneration(aGeneration)
+{
+}
+
+FrameLayerBuilder::ClippedDisplayItem::~ClippedDisplayItem()
+{
+ if (mInactiveLayerManager) {
+ mInactiveLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
+ }
+}
+
+FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayer *aKey)
+ : nsPtrHashKey<PaintedLayer>(aKey)
+ , mContainerLayerFrame(nullptr)
+ , mLastCommonClipCount(0)
+ , mContainerLayerGeneration(0)
+ , mHasExplicitLastPaintOffset(false)
+ , mCommonClipCount(0)
+{
+}
+
+FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayerItemsEntry& aOther)
+ : nsPtrHashKey<PaintedLayer>(aOther.mKey)
+ , mItems(aOther.mItems)
+{
+ NS_ERROR("Should never be called, since we ALLOW_MEMMOVE");
+}
+
+FrameLayerBuilder::PaintedLayerItemsEntry::~PaintedLayerItemsEntry()
+{
+}
+
+void
+FrameLayerBuilder::AddLayerDisplayItem(Layer* aLayer,
+ nsDisplayItem* aItem,
+ LayerState aLayerState,
+ BasicLayerManager* aManager)
+{
+ if (aLayer->Manager() != mRetainingManager)
+ return;
+
+ DisplayItemData *data = StoreDataForFrame(aItem, aLayer, aLayerState);
+ data->mInactiveManager = aManager;
+}
+
+nsIntPoint
+FrameLayerBuilder::GetLastPaintOffset(PaintedLayer* aLayer)
+{
+ PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(aLayer);
+ if (entry) {
+ if (entry->mContainerLayerGeneration == 0) {
+ entry->mContainerLayerGeneration = mContainerLayerGeneration;
+ }
+ if (entry->mHasExplicitLastPaintOffset)
+ return entry->mLastPaintOffset;
+ }
+ return GetTranslationForPaintedLayer(aLayer);
+}
+
+void
+FrameLayerBuilder::SavePreviousDataForLayer(PaintedLayer* aLayer, uint32_t aClipCount)
+{
+ PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(aLayer);
+ if (entry) {
+ if (entry->mContainerLayerGeneration == 0) {
+ entry->mContainerLayerGeneration = mContainerLayerGeneration;
+ }
+ entry->mLastPaintOffset = GetTranslationForPaintedLayer(aLayer);
+ entry->mHasExplicitLastPaintOffset = true;
+ entry->mLastCommonClipCount = aClipCount;
+ }
+}
+
+bool
+FrameLayerBuilder::CheckInLayerTreeCompressionMode()
+{
+ if (mInLayerTreeCompressionMode) {
+ return true;
+ }
+
+ // If we wanted to be in layer tree compression mode, but weren't, then scheduled
+ // a delayed repaint where we will be.
+ mRootPresContext->PresShell()->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DELAYED_COMPRESS);
+
+ return false;
+}
+
+void
+ContainerState::CollectOldLayers()
+{
+ for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
+ layer = layer->GetNextSibling()) {
+ NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData),
+ "Mask layers should not be part of the layer tree.");
+ if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type");
+ mPaintedLayersAvailableForRecycling.PutEntry(static_cast<PaintedLayer*>(layer));
+ }
+
+ if (Layer* maskLayer = layer->GetMaskLayer()) {
+ NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
+ "Could not recycle mask layer, unsupported layer type.");
+ mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Nothing()), static_cast<ImageLayer*>(maskLayer));
+ }
+ for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) {
+ Layer* maskLayer = layer->GetAncestorMaskLayerAt(i);
+
+ NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
+ "Could not recycle mask layer, unsupported layer type.");
+ mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Some(i)), static_cast<ImageLayer*>(maskLayer));
+ }
+ }
+}
+
+struct OpaqueRegionEntry {
+ AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ nsIntRegion mOpaqueRegion;
+};
+
+static OpaqueRegionEntry*
+FindOpaqueRegionEntry(nsTArray<OpaqueRegionEntry>& aEntries,
+ AnimatedGeometryRoot* aAnimatedGeometryRoot)
+{
+ for (uint32_t i = 0; i < aEntries.Length(); ++i) {
+ OpaqueRegionEntry* d = &aEntries[i];
+ if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot) {
+ return d;
+ }
+ }
+ return nullptr;
+}
+
+void
+ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry)
+{
+ if (mFlattenToSingleLayer) {
+ // animated geometry roots are forced to all match, so we can't
+ // use them and we don't get async scrolling.
+ return;
+ }
+
+ if (!mBuilder->IsPaintingToWindow()) {
+ // async scrolling not possible, and async scrolling info not computed
+ // for this paint.
+ return;
+ }
+
+ AutoTArray<ScrollMetadata,2> metricsArray;
+ if (aEntry->mBaseScrollMetadata) {
+ metricsArray.AppendElement(*aEntry->mBaseScrollMetadata);
+
+ // The base FrameMetrics was not computed by the nsIScrollableframe, so it
+ // should not have a mask layer.
+ MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer());
+ }
+
+ // Any extra mask layers we need to attach to ScrollMetadatas.
+ // The list may already contain an entry added for the layer's scrolled clip
+ // so add to it rather than overwriting it (we clear the list when recycling
+ // a layer).
+ nsTArray<RefPtr<Layer>> maskLayers(aEntry->mLayer->GetAllAncestorMaskLayers());
+
+ for (const DisplayItemScrollClip* scrollClip = aEntry->mScrollClip;
+ scrollClip && scrollClip != mContainerScrollClip;
+ scrollClip = scrollClip->mParent) {
+ if (!scrollClip->mIsAsyncScrollable) {
+ // This scroll clip was created for a scroll frame that didn't know
+ // whether it needs to be async scrollable for scroll handoff. It was
+ // not activated, so we don't need to create a frame metrics for it.
+ continue;
+ }
+
+ nsIScrollableFrame* scrollFrame = scrollClip->mScrollableFrame;
+ const DisplayItemClip* clip = scrollClip->mClip;
+
+ Maybe<ScrollMetadata> metadata =
+ scrollFrame->ComputeScrollMetadata(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip);
+ if (!metadata) {
+ continue;
+ }
+
+ if (clip &&
+ clip->HasClip() &&
+ clip->GetRoundedRectCount() > 0)
+ {
+ // The clip in between this scrollframe and its ancestor scrollframe
+ // requires a mask layer. Since this mask layer should not move with
+ // the APZC associated with this FrameMetrics, we attach the mask
+ // layer as an additional, separate clip.
+ Maybe<size_t> nextIndex = Some(maskLayers.Length());
+ RefPtr<Layer> maskLayer =
+ CreateMaskLayer(aEntry->mLayer, *clip, nextIndex, clip->GetRoundedRectCount());
+ if (maskLayer) {
+ MOZ_ASSERT(metadata->HasScrollClip());
+ metadata->ScrollClip().SetMaskLayerIndex(nextIndex);
+ maskLayers.AppendElement(maskLayer);
+ }
+ }
+
+ metricsArray.AppendElement(*metadata);
+ }
+
+ // Watch out for FrameMetrics copies in profiles
+ aEntry->mLayer->SetScrollMetadata(metricsArray);
+ aEntry->mLayer->SetAncestorMaskLayers(maskLayers);
+}
+
+static inline Maybe<ParentLayerIntRect>
+GetStationaryClipInContainer(Layer* aLayer)
+{
+ if (size_t metricsCount = aLayer->GetScrollMetadataCount()) {
+ return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect();
+ }
+ return aLayer->GetClipRect();
+}
+
+void
+ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer)
+{
+ AutoTArray<OpaqueRegionEntry,4> opaqueRegions;
+ bool hideAll = false;
+ int32_t opaqueRegionForContainer = -1;
+
+ for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) {
+ NewLayerEntry* e = &mNewChildLayers.ElementAt(i);
+ if (!e->mLayer) {
+ continue;
+ }
+
+ OpaqueRegionEntry* data = FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot);
+
+ SetupScrollingMetadata(e);
+
+ if (hideAll) {
+ e->mVisibleRegion.SetEmpty();
+ } else if (!e->mLayer->IsScrollbarContainer()) {
+ Maybe<ParentLayerIntRect> clipRect = GetStationaryClipInContainer(e->mLayer);
+ if (clipRect && opaqueRegionForContainer >= 0 &&
+ opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(clipRect->ToUnknownRect())) {
+ e->mVisibleRegion.SetEmpty();
+ } else if (data) {
+ e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
+ }
+ }
+
+ SetOuterVisibleRegionForLayer(e->mLayer,
+ e->mVisibleRegion,
+ e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr,
+ e->mUntransformedVisibleRegion);
+
+ if (!e->mOpaqueRegion.IsEmpty()) {
+ AnimatedGeometryRoot* animatedGeometryRootToCover = e->mAnimatedGeometryRoot;
+ if (e->mOpaqueForAnimatedGeometryRootParent &&
+ e->mAnimatedGeometryRoot->mParentAGR == mContainerAnimatedGeometryRoot) {
+ animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
+ data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover);
+ }
+
+ if (!data) {
+ if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot) {
+ NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?");
+ opaqueRegionForContainer = opaqueRegions.Length();
+ }
+ data = opaqueRegions.AppendElement();
+ data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
+ }
+
+ nsIntRegion clippedOpaque = e->mOpaqueRegion;
+ Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect();
+ if (clipRect) {
+ clippedOpaque.AndWith(clipRect->ToUnknownRect());
+ }
+ if (e->mLayer->GetIsFixedPosition() && e->mLayer->GetScrolledClip()) {
+ // The clip can move asynchronously, so we can't rely on opaque parts
+ // staying in the same place.
+ clippedOpaque.SetEmpty();
+ } else if (e->mHideAllLayersBelow) {
+ hideAll = true;
+ }
+ data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque);
+ }
+
+ if (e->mLayer->GetType() == Layer::TYPE_READBACK) {
+ // ReadbackLayers need to accurately read what's behind them. So,
+ // we don't want to do any occlusion culling of layers behind them.
+ // Theoretically we could just punch out the ReadbackLayer's rectangle
+ // from all mOpaqueRegions, but that's probably not worth doing.
+ opaqueRegions.Clear();
+ opaqueRegionForContainer = -1;
+ }
+ }
+
+ if (opaqueRegionForContainer >= 0) {
+ aOpaqueRegionForContainer->Or(*aOpaqueRegionForContainer,
+ opaqueRegions[opaqueRegionForContainer].mOpaqueRegion);
+ }
+}
+
+void
+ContainerState::Finish(uint32_t* aTextContentFlags,
+ const nsIntRect& aContainerPixelBounds,
+ nsDisplayList* aChildItems, bool* aHasComponentAlphaChildren)
+{
+ mPaintedLayerDataTree.Finish();
+
+ if (!mParameters.mForEventsAndPluginsOnly) {
+ NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
+ "Bounds computation mismatch");
+ }
+
+ if (mLayerBuilder->IsBuildingRetainedLayers()) {
+ nsIntRegion containerOpaqueRegion;
+ PostprocessRetainedLayers(&containerOpaqueRegion);
+ if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
+ aChildItems->SetIsOpaque();
+ }
+ }
+
+ uint32_t textContentFlags = 0;
+
+ // Make sure that current/existing layers are added to the parent and are
+ // in the correct order.
+ Layer* layer = nullptr;
+ Layer* prevChild = nullptr;
+ for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) {
+ if (!mNewChildLayers[i].mLayer) {
+ continue;
+ }
+
+ layer = mNewChildLayers[i].mLayer;
+
+ if (!layer->GetVisibleRegion().IsEmpty()) {
+ textContentFlags |=
+ layer->GetContentFlags() & (Layer::CONTENT_COMPONENT_ALPHA |
+ Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT |
+ Layer::CONTENT_DISABLE_FLATTENING);
+
+ // Notify the parent of component alpha children unless it's coming from
+ // within a child that has asked not to contribute to layer flattening.
+ if (aHasComponentAlphaChildren &&
+ mNewChildLayers[i].mPropagateComponentAlphaFlattening &&
+ (layer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA)) {
+
+ for (int32_t j = i - 1; j >= 0; j--) {
+ if (mNewChildLayers[j].mVisibleRegion.Intersects(mNewChildLayers[i].mVisibleRegion.GetBounds())) {
+ if (mNewChildLayers[j].mLayerState != LAYER_ACTIVE_FORCE) {
+ *aHasComponentAlphaChildren = true;
+ }
+ break;
+
+ }
+
+ }
+ }
+ }
+
+ if (!layer->GetParent()) {
+ // This is not currently a child of the container, so just add it
+ // now.
+ mContainerLayer->InsertAfter(layer, prevChild);
+ } else {
+ NS_ASSERTION(layer->GetParent() == mContainerLayer,
+ "Layer shouldn't be the child of some other container");
+ if (layer->GetPrevSibling() != prevChild) {
+ mContainerLayer->RepositionChild(layer, prevChild);
+ }
+ }
+ }
+
+ // Remove old layers that have become unused.
+ if (!layer) {
+ layer = mContainerLayer->GetFirstChild();
+ } else {
+ layer = layer->GetNextSibling();
+ }
+ while (layer) {
+ Layer *layerToRemove = layer;
+ layer = layer->GetNextSibling();
+ mContainerLayer->RemoveChild(layerToRemove);
+ }
+
+ *aTextContentFlags = textContentFlags;
+}
+
+static inline gfxSize RoundToFloatPrecision(const gfxSize& aSize)
+{
+ return gfxSize(float(aSize.width), float(aSize.height));
+}
+
+static void RestrictScaleToMaxLayerSize(gfxSize& aScale,
+ const nsRect& aVisibleRect,
+ nsIFrame* aContainerFrame,
+ Layer* aContainerLayer)
+{
+ if (!aContainerLayer->Manager()->IsWidgetLayerManager()) {
+ return;
+ }
+
+ nsIntRect pixelSize =
+ aVisibleRect.ScaleToOutsidePixels(aScale.width, aScale.height,
+ aContainerFrame->PresContext()->AppUnitsPerDevPixel());
+
+ int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize();
+
+ if (pixelSize.width > maxLayerSize) {
+ float scale = (float)pixelSize.width / maxLayerSize;
+ scale = gfxUtils::ClampToScaleFactor(scale);
+ aScale.width /= scale;
+ }
+ if (pixelSize.height > maxLayerSize) {
+ float scale = (float)pixelSize.height / maxLayerSize;
+ scale = gfxUtils::ClampToScaleFactor(scale);
+ aScale.height /= scale;
+ }
+}
+static bool
+ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ const nsRect& aVisibleRect,
+ const Matrix4x4* aTransform,
+ const ContainerLayerParameters& aIncomingScale,
+ ContainerLayer* aLayer,
+ LayerState aState,
+ ContainerLayerParameters& aOutgoingScale)
+{
+ nsIntPoint offset;
+
+ Matrix4x4 transform =
+ Matrix4x4::Scaling(aIncomingScale.mXScale, aIncomingScale.mYScale, 1.0);
+ if (aTransform) {
+ // aTransform is applied first, then the scale is applied to the result
+ transform = (*aTransform)*transform;
+ // Set any matrix entries close to integers to be those exact integers.
+ // This protects against floating-point inaccuracies causing problems
+ // in the checks below.
+ // We use the fixed epsilon version here because we don't want the nudging
+ // to depend on the scroll position.
+ transform.NudgeToIntegersFixedEpsilon();
+ }
+ Matrix transform2d;
+ if (aContainerFrame &&
+ (aState == LAYER_INACTIVE || aState == LAYER_SVG_EFFECTS) &&
+ (!aTransform || (aTransform->Is2D(&transform2d) &&
+ !transform2d.HasNonTranslation()))) {
+ // When we have an inactive ContainerLayer, translate the container by the offset to the
+ // reference frame (and offset all child layers by the reverse) so that the coordinate
+ // space of the child layers isn't affected by scrolling.
+ // This gets confusing for complicated transform (since we'd have to compute the scale
+ // factors for the matrix), so we don't bother. Any frames that are building an nsDisplayTransform
+ // for a css transform would have 0,0 as their offset to the reference frame, so this doesn't
+ // matter.
+ nsPoint appUnitOffset = aDisplayListBuilder->ToReferenceFrame(aContainerFrame);
+ nscoord appUnitsPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel();
+ offset = nsIntPoint(
+ NS_lround(NSAppUnitsToDoublePixels(appUnitOffset.x, appUnitsPerDevPixel)*aIncomingScale.mXScale),
+ NS_lround(NSAppUnitsToDoublePixels(appUnitOffset.y, appUnitsPerDevPixel)*aIncomingScale.mYScale));
+ }
+ transform.PostTranslate(offset.x + aIncomingScale.mOffset.x,
+ offset.y + aIncomingScale.mOffset.y,
+ 0);
+
+ if (transform.IsSingular()) {
+ return false;
+ }
+
+ bool canDraw2D = transform.CanDraw2D(&transform2d);
+ gfxSize scale;
+ // XXX Should we do something for 3D transforms?
+ if (canDraw2D &&
+ !aContainerFrame->Combines3DTransformWithAncestors() &&
+ !aContainerFrame->HasPerspective()) {
+ // If the container's transform is animated off main thread, fix a suitable scale size
+ // for animation
+ if (aContainerItem &&
+ aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
+ EffectCompositor::HasAnimationsForCompositor(
+ aContainerFrame, eCSSProperty_transform)) {
+ // Use the size of the nearest widget as the maximum size. This
+ // is important since it might be a popup that is bigger than the
+ // pres context's size.
+ nsPresContext* presContext = aContainerFrame->PresContext();
+ nsIWidget* widget = aContainerFrame->GetNearestWidget();
+ nsSize displaySize;
+ if (widget) {
+ LayoutDeviceIntSize widgetSize = widget->GetClientSize();
+ int32_t p2a = presContext->AppUnitsPerDevPixel();
+ displaySize.width = NSIntPixelsToAppUnits(widgetSize.width, p2a);
+ displaySize.height = NSIntPixelsToAppUnits(widgetSize.height, p2a);
+ } else {
+ displaySize = presContext->GetVisibleArea().Size();
+ }
+ // compute scale using the animation on the container (ignoring
+ // its ancestors)
+ scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
+ aContainerFrame, aVisibleRect.Size(),
+ displaySize);
+ // multiply by the scale inherited from ancestors--we use a uniform
+ // scale factor to prevent blurring when the layer is rotated.
+ float incomingScale = std::max(aIncomingScale.mXScale, aIncomingScale.mYScale);
+ scale.width *= incomingScale;
+ scale.height *= incomingScale;
+ } else {
+ // Scale factors are normalized to a power of 2 to reduce the number of resolution changes
+ scale = RoundToFloatPrecision(ThebesMatrix(transform2d).ScaleFactors(true));
+ // For frames with a changing transform that's not just a translation,
+ // round scale factors up to nearest power-of-2 boundary so that we don't
+ // keep having to redraw the content as it scales up and down. Rounding up to nearest
+ // power-of-2 boundary ensures we never scale up, only down --- avoiding
+ // jaggies. It also ensures we never scale down by more than a factor of 2,
+ // avoiding bad downscaling quality.
+ Matrix frameTransform;
+ if (ActiveLayerTracker::IsStyleAnimated(aDisplayListBuilder, aContainerFrame, eCSSProperty_transform) &&
+ aTransform &&
+ (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) {
+ // Don't clamp the scale factor when the new desired scale factor matches the old one
+ // or it was previously unscaled.
+ bool clamp = true;
+ Matrix oldFrameTransform2d;
+ if (aLayer->GetBaseTransform().Is2D(&oldFrameTransform2d)) {
+ gfxSize oldScale = RoundToFloatPrecision(ThebesMatrix(oldFrameTransform2d).ScaleFactors(true));
+ if (oldScale == scale || oldScale == gfxSize(1.0, 1.0)) {
+ clamp = false;
+ }
+ }
+ if (clamp) {
+ scale.width = gfxUtils::ClampToScaleFactor(scale.width);
+ scale.height = gfxUtils::ClampToScaleFactor(scale.height);
+ }
+ } else {
+ // XXX Do we need to move nearly-integer values to integers here?
+ }
+ }
+ // If the scale factors are too small, just use 1.0. The content is being
+ // scaled out of sight anyway.
+ if (fabs(scale.width) < 1e-8 || fabs(scale.height) < 1e-8) {
+ scale = gfxSize(1.0, 1.0);
+ }
+ // If this is a transform container layer, then pre-rendering might
+ // mean we try render a layer bigger than the max texture size. If we have
+ // tiling, that's not a problem, since we'll automatically choose a tiled
+ // layer for layers of that size. If not, we need to apply clamping to
+ // prevent this.
+ if (aTransform && !gfxPrefs::LayersTilesEnabled()) {
+ RestrictScaleToMaxLayerSize(scale, aVisibleRect, aContainerFrame, aLayer);
+ }
+ } else {
+ scale = gfxSize(1.0, 1.0);
+ }
+
+ // Store the inverse of our resolution-scale on the layer
+ aLayer->SetBaseTransform(transform);
+ aLayer->SetPreScale(1.0f/float(scale.width),
+ 1.0f/float(scale.height));
+ aLayer->SetInheritedScale(aIncomingScale.mXScale,
+ aIncomingScale.mYScale);
+
+ aOutgoingScale =
+ ContainerLayerParameters(scale.width, scale.height, -offset, aIncomingScale);
+ if (aTransform) {
+ aOutgoingScale.mInTransformedSubtree = true;
+ if (ActiveLayerTracker::IsStyleAnimated(aDisplayListBuilder, aContainerFrame,
+ eCSSProperty_transform)) {
+ aOutgoingScale.mInActiveTransformedSubtree = true;
+ }
+ }
+ if ((aLayerBuilder->IsBuildingRetainedLayers() &&
+ (!canDraw2D || transform2d.HasNonIntegerTranslation())) ||
+ aContainerFrame->Extend3DContext() ||
+ aContainerFrame->Combines3DTransformWithAncestors()) {
+ aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true;
+ }
+ return true;
+}
+
+already_AddRefed<ContainerLayer>
+FrameLayerBuilder::BuildContainerLayerFor(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ nsDisplayList* aChildren,
+ const ContainerLayerParameters& aParameters,
+ const Matrix4x4* aTransform,
+ uint32_t aFlags)
+{
+ uint32_t containerDisplayItemKey =
+ aContainerItem ? aContainerItem->GetPerFrameKey() : nsDisplayItem::TYPE_ZERO;
+ NS_ASSERTION(aContainerFrame, "Container display items here should have a frame");
+ NS_ASSERTION(!aContainerItem ||
+ aContainerItem->Frame() == aContainerFrame,
+ "Container display item must match given frame");
+
+ if (!aParameters.mXScale || !aParameters.mYScale) {
+ return nullptr;
+ }
+
+ RefPtr<ContainerLayer> containerLayer;
+ if (aManager == mRetainingManager) {
+ // Using GetOldLayerFor will search merged frames, as well as the underlying
+ // frame. The underlying frame can change when a page scrolls, so this
+ // avoids layer recreation in the situation that a new underlying frame is
+ // picked for a layer.
+ Layer* oldLayer = nullptr;
+ if (aContainerItem) {
+ oldLayer = GetOldLayerFor(aContainerItem);
+ } else {
+ DisplayItemData *data = GetOldLayerForFrame(aContainerFrame, containerDisplayItemKey);
+ if (data) {
+ oldLayer = data->mLayer;
+ }
+ }
+
+ if (oldLayer) {
+ NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager");
+ if (oldLayer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ // The old layer for this item is actually our PaintedLayer
+ // because we rendered its layer into that PaintedLayer. So we
+ // don't actually have a retained container layer.
+ } else {
+ NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER,
+ "Wrong layer type");
+ containerLayer = static_cast<ContainerLayer*>(oldLayer);
+ ResetLayerStateForRecycling(containerLayer);
+ }
+ }
+ }
+ if (!containerLayer) {
+ // No suitable existing layer was found.
+ containerLayer = aManager->CreateContainerLayer();
+ if (!containerLayer)
+ return nullptr;
+ }
+
+ LayerState state = aContainerItem ? aContainerItem->GetLayerState(aBuilder, aManager, aParameters) : LAYER_ACTIVE;
+ if (state == LAYER_INACTIVE &&
+ nsDisplayItem::ForceActiveLayers()) {
+ state = LAYER_ACTIVE;
+ }
+
+ if (aContainerItem && state == LAYER_ACTIVE_EMPTY) {
+ // Empty layers only have metadata and should never have display items. We
+ // early exit because later, invalidation will walk up the frame tree to
+ // determine which painted layer gets invalidated. Since an empty layer
+ // should never have anything to paint, it should never be invalidated.
+ NS_ASSERTION(aChildren->IsEmpty(), "Should have no children");
+ return containerLayer.forget();
+ }
+
+ const DisplayItemScrollClip* containerScrollClip = aParameters.mScrollClip;
+
+ ContainerLayerParameters scaleParameters;
+ nsRect bounds = aChildren->GetScrollClippedBoundsUpTo(aBuilder, containerScrollClip);
+ nsRect childrenVisible =
+ aContainerItem ? aContainerItem->GetVisibleRectForChildren() :
+ aContainerFrame->GetVisualOverflowRectRelativeToSelf();
+ if (!ChooseScaleAndSetTransform(this, aBuilder, aContainerFrame,
+ aContainerItem,
+ bounds.Intersect(childrenVisible),
+ aTransform, aParameters,
+ containerLayer, state, scaleParameters)) {
+ return nullptr;
+ }
+
+ uint32_t oldGeneration = mContainerLayerGeneration;
+ mContainerLayerGeneration = ++mMaxContainerLayerGeneration;
+
+ if (mRetainingManager) {
+ if (aContainerItem) {
+ StoreDataForFrame(aContainerItem, containerLayer, LAYER_ACTIVE);
+ } else {
+ StoreDataForFrame(aContainerFrame, containerDisplayItemKey, containerLayer, LAYER_ACTIVE);
+ }
+ }
+
+ LayerManagerData* data = static_cast<LayerManagerData*>
+ (aManager->GetUserData(&gLayerManagerUserData));
+
+ nsIntRect pixBounds;
+ nscoord appUnitsPerDevPixel;
+ bool flattenToSingleLayer = false;
+ if ((aContainerFrame->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) &&
+ mRetainingManager &&
+ mRetainingManager->ShouldAvoidComponentAlphaLayers() &&
+ !nsLayoutUtils::AsyncPanZoomEnabled(aContainerFrame))
+ {
+ flattenToSingleLayer = true;
+ }
+
+ nscolor backgroundColor = NS_RGBA(0,0,0,0);
+ if (aFlags & CONTAINER_ALLOW_PULL_BACKGROUND_COLOR) {
+ backgroundColor = aParameters.mBackgroundColor;
+ }
+
+ uint32_t flags;
+ while (true) {
+ ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(),
+ aContainerFrame, aContainerItem, bounds,
+ containerLayer, scaleParameters, flattenToSingleLayer,
+ backgroundColor, containerScrollClip);
+
+ state.ProcessDisplayItems(aChildren);
+
+ // Set CONTENT_COMPONENT_ALPHA if any of our children have it.
+ // This is suboptimal ... a child could have text that's over transparent
+ // pixels in its own layer, but over opaque parts of previous siblings.
+ bool hasComponentAlphaChildren = false;
+ bool mayFlatten =
+ mRetainingManager &&
+ mRetainingManager->ShouldAvoidComponentAlphaLayers() &&
+ !flattenToSingleLayer &&
+ !nsLayoutUtils::AsyncPanZoomEnabled(aContainerFrame);
+
+ pixBounds = state.ScaleToOutsidePixels(bounds, false);
+ appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel();
+ state.Finish(&flags, pixBounds, aChildren, mayFlatten ? &hasComponentAlphaChildren : nullptr);
+
+ if (hasComponentAlphaChildren &&
+ !(flags & Layer::CONTENT_DISABLE_FLATTENING) &&
+ containerLayer->HasMultipleChildren())
+ {
+ // Since we don't want any component alpha layers on BasicLayers, we repeat
+ // the layer building process with this explicitely forced off.
+ // We restore the previous FrameLayerBuilder state since the first set
+ // of layer building will have changed it.
+ flattenToSingleLayer = true;
+
+ // Restore DisplayItemData
+ for (auto iter = data->mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
+ DisplayItemData* data = iter.Get()->GetKey();
+ if (data->mUsed && data->mContainerLayerGeneration >= mContainerLayerGeneration) {
+ iter.Remove();
+ }
+ }
+
+ // Restore PaintedLayerItemEntries
+ for (auto iter = mPaintedLayerItems.Iter(); !iter.Done(); iter.Next()) {
+ PaintedLayerItemsEntry* entry = iter.Get();
+ if (entry->mContainerLayerGeneration >= mContainerLayerGeneration) {
+ // We can just remove these items rather than attempting to revert them
+ // because we're going to want to invalidate everything when transitioning
+ // to component alpha flattening.
+ iter.Remove();
+ continue;
+ }
+
+ for (uint32_t i = 0; i < entry->mItems.Length(); i++) {
+ if (entry->mItems[i].mContainerLayerGeneration >= mContainerLayerGeneration) {
+ entry->mItems.TruncateLength(i);
+ break;
+ }
+ }
+ }
+
+ aContainerFrame->AddStateBits(NS_FRAME_NO_COMPONENT_ALPHA);
+ continue;
+ }
+ break;
+ }
+
+ // CONTENT_COMPONENT_ALPHA is propogated up to the nearest CONTENT_OPAQUE
+ // ancestor so that BasicLayerManager knows when to copy the background into
+ // pushed groups. Accelerated layers managers can't necessarily do this (only
+ // when the visible region is a simple rect), so we propogate
+ // CONTENT_COMPONENT_ALPHA_DESCENDANT all the way to the root.
+ if (flags & Layer::CONTENT_COMPONENT_ALPHA) {
+ flags |= Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT;
+ }
+
+ // Make sure that rounding the visible region out didn't add any area
+ // we won't paint
+ if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) {
+ bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale);
+ if (bounds.Contains(ToAppUnits(pixBounds, appUnitsPerDevPixel))) {
+ // Clear CONTENT_COMPONENT_ALPHA and add CONTENT_OPAQUE instead.
+ flags &= ~Layer::CONTENT_COMPONENT_ALPHA;
+ flags |= Layer::CONTENT_OPAQUE;
+ }
+ }
+ containerLayer->SetContentFlags(flags);
+ // If aContainerItem is non-null some BuildContainerLayer further up the
+ // call stack is responsible for setting containerLayer's visible region.
+ if (!aContainerItem) {
+ containerLayer->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(pixBounds));
+ }
+ if (aParameters.mLayerContentsVisibleRect) {
+ *aParameters.mLayerContentsVisibleRect = pixBounds + scaleParameters.mOffset;
+ }
+
+ mContainerLayerGeneration = oldGeneration;
+ nsPresContext::ClearNotifySubDocInvalidationData(containerLayer);
+
+ return containerLayer.forget();
+}
+
+Layer*
+FrameLayerBuilder::GetLeafLayerFor(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem)
+{
+ Layer* layer = GetOldLayerFor(aItem);
+ if (!layer)
+ return nullptr;
+ if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ // This layer was created to render Thebes-rendered content for this
+ // display item. The display item should not use it for its own
+ // layer rendering.
+ return nullptr;
+ }
+ ResetLayerStateForRecycling(layer);
+ return layer;
+}
+
+/* static */ void
+FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager)
+{
+ LayerManagerData* data = static_cast<LayerManagerData*>
+ (aManager->GetUserData(&gLayerManagerUserData));
+ if (data) {
+ data->mInvalidateAllLayers = true;
+ }
+}
+
+/* static */ void
+FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame *aFrame)
+{
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+ if (array) {
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ AssertDisplayItemData(array->ElementAt(i))->mParent->mInvalidateAllLayers = true;
+ }
+ }
+}
+
+/* static */
+Layer*
+FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame, uint32_t aDisplayItemKey)
+{
+ //TODO: This isn't completely correct, since a frame could exist as a layer
+ // in the normal widget manager, and as a different layer (or no layer)
+ // in the secondary manager
+
+ const nsTArray<DisplayItemData*>* array =
+ aFrame->Properties().Get(LayerManagerDataProperty());
+ if (array) {
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ DisplayItemData *element = AssertDisplayItemData(array->ElementAt(i));
+ if (!element->mParent->mLayerManager->IsWidgetLayerManager()) {
+ continue;
+ }
+ if (element->mDisplayItemKey == aDisplayItemKey) {
+ if (element->mOptLayer) {
+ return element->mOptLayer;
+ }
+
+ Layer* layer = element->mLayer;
+ if (!layer->HasUserData(&gColorLayerUserData) &&
+ !layer->HasUserData(&gImageLayerUserData) &&
+ !layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
+ return layer;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+static gfxSize
+PredictScaleForContent(nsIFrame* aFrame, nsIFrame* aAncestorWithScale,
+ const gfxSize& aScale)
+{
+ Matrix4x4 transform = Matrix4x4::Scaling(aScale.width, aScale.height, 1.0);
+ if (aFrame != aAncestorWithScale) {
+ // aTransform is applied first, then the scale is applied to the result
+ transform = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestorWithScale)*transform;
+ }
+ Matrix transform2d;
+ if (transform.CanDraw2D(&transform2d)) {
+ return ThebesMatrix(transform2d).ScaleFactors(true);
+ }
+ return gfxSize(1.0, 1.0);
+}
+
+gfxSize
+FrameLayerBuilder::GetPaintedLayerScaleForFrame(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame, "need a frame");
+ nsIFrame* last = nullptr;
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ last = f;
+
+ if (nsLayoutUtils::IsPopup(f)) {
+ // Don't examine ancestors of a popup. It won't make sense to check
+ // the transform from some content inside the popup to some content
+ // which is an ancestor of the popup.
+ break;
+ }
+
+ const nsTArray<DisplayItemData*>* array =
+ f->Properties().Get(LayerManagerDataProperty());
+ if (!array) {
+ continue;
+ }
+
+ for (uint32_t i = 0; i < array->Length(); i++) {
+ Layer* layer = AssertDisplayItemData(array->ElementAt(i))->mLayer;
+ ContainerLayer* container = layer->AsContainerLayer();
+ if (!container ||
+ !layer->Manager()->IsWidgetLayerManager()) {
+ continue;
+ }
+ for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) {
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>
+ (l->GetUserData(&gPaintedDisplayItemLayerUserData));
+ if (data) {
+ return PredictScaleForContent(aFrame, f, gfxSize(data->mXScale, data->mYScale));
+ }
+ }
+ }
+ }
+
+ float presShellResolution = last->PresContext()->PresShell()->GetResolution();
+ return PredictScaleForContent(aFrame, last,
+ gfxSize(presShellResolution, presShellResolution));
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void DebugPaintItem(DrawTarget& aDrawTarget,
+ nsPresContext* aPresContext,
+ nsDisplayItem *aItem,
+ nsDisplayListBuilder* aBuilder)
+{
+ bool snap;
+ Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap),
+ aPresContext->AppUnitsPerDevPixel());
+
+ RefPtr<DrawTarget> tempDT =
+ aDrawTarget.CreateSimilarDrawTarget(IntSize::Truncate(bounds.width, bounds.height),
+ SurfaceFormat::B8G8R8A8);
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT);
+ if (!context) {
+ // Leave this as crash, it's in the debugging code, we want to know
+ gfxDevCrash(LogReason::InvalidContext) << "DebugPaintItem context problem " << gfx::hexa(tempDT);
+ return;
+ }
+ context->SetMatrix(gfxMatrix::Translation(-bounds.x, -bounds.y));
+ nsRenderingContext ctx(context);
+
+ aItem->Paint(aBuilder, &ctx);
+ RefPtr<SourceSurface> surface = tempDT->Snapshot();
+ DumpPaintedImage(aItem, surface);
+
+ aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0,0), bounds.Size()));
+
+ aItem->SetPainted();
+}
+#endif
+
+/* static */ void
+FrameLayerBuilder::RecomputeVisibilityForItems(nsTArray<ClippedDisplayItem>& aItems,
+ nsDisplayListBuilder *aBuilder,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntPoint& aOffset,
+ int32_t aAppUnitsPerDevPixel,
+ float aXScale,
+ float aYScale)
+{
+ uint32_t i;
+ // Update visible regions. We perform visibility analysis to take account
+ // of occlusion culling.
+ nsRegion visible = aRegionToDraw.ToAppUnits(aAppUnitsPerDevPixel);
+ visible.MoveBy(NSIntPixelsToAppUnits(aOffset.x, aAppUnitsPerDevPixel),
+ NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel));
+ visible.ScaleInverseRoundOut(aXScale, aYScale);
+
+ for (i = aItems.Length(); i > 0; --i) {
+ ClippedDisplayItem* cdi = &aItems[i - 1];
+ const DisplayItemClip& clip = cdi->mItem->GetClip();
+
+ NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel,
+ "a painted layer should contain items only at the same zoom");
+
+ MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0,
+ "If we have rounded rects, we must have a clip rect");
+
+ if (!clip.IsRectAffectedByClip(visible.GetBounds())) {
+ cdi->mItem->RecomputeVisibility(aBuilder, &visible);
+ continue;
+ }
+
+ // Do a little dance to account for the fact that we're clipping
+ // to cdi->mClipRect
+ nsRegion clipped;
+ clipped.And(visible, clip.NonRoundedIntersection());
+ nsRegion finalClipped = clipped;
+ cdi->mItem->RecomputeVisibility(aBuilder, &finalClipped);
+ // If we have rounded clip rects, don't subtract from the visible
+ // region since we aren't displaying everything inside the rect.
+ if (clip.GetRoundedRectCount() == 0) {
+ nsRegion removed;
+ removed.Sub(clipped, finalClipped);
+ nsRegion newVisible;
+ newVisible.Sub(visible, removed);
+ // Don't let the visible region get too complex.
+ if (newVisible.GetNumRects() <= 15) {
+ visible = newVisible;
+ }
+ }
+ }
+}
+
+void
+FrameLayerBuilder::PaintItems(nsTArray<ClippedDisplayItem>& aItems,
+ const nsIntRect& aRect,
+ gfxContext *aContext,
+ nsRenderingContext *aRC,
+ nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ const nsIntPoint& aOffset,
+ float aXScale, float aYScale,
+ int32_t aCommonClipCount)
+{
+ DrawTarget& aDrawTarget = *aRC->GetDrawTarget();
+
+ int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+ nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel);
+ boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel),
+ NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel));
+ boundRect.ScaleInverseRoundOut(aXScale, aYScale);
+
+ DisplayItemClip currentClip;
+ bool currentClipIsSetInContext = false;
+ DisplayItemClip tmpClip;
+
+ for (uint32_t i = 0; i < aItems.Length(); ++i) {
+ ClippedDisplayItem* cdi = &aItems[i];
+
+ nsRect paintRect = cdi->mItem->GetVisibleRect().Intersect(boundRect);
+ if (paintRect.IsEmpty())
+ continue;
+
+#ifdef MOZ_DUMP_PAINTING
+ PROFILER_LABEL_PRINTF("DisplayList", "Draw",
+ js::ProfileEntry::Category::GRAPHICS, "%s", cdi->mItem->Name());
+#else
+ PROFILER_LABEL("DisplayList", "Draw",
+ js::ProfileEntry::Category::GRAPHICS);
+#endif
+
+ // If the new desired clip state is different from the current state,
+ // update the clip.
+ const DisplayItemClip* clip = &cdi->mItem->GetClip();
+ if (clip->GetRoundedRectCount() > 0 &&
+ !clip->IsRectClippedByRoundedCorner(cdi->mItem->GetVisibleRect())) {
+ tmpClip = *clip;
+ tmpClip.RemoveRoundedCorners();
+ clip = &tmpClip;
+ }
+ if (currentClipIsSetInContext != clip->HasClip() ||
+ (clip->HasClip() && *clip != currentClip)) {
+ if (currentClipIsSetInContext) {
+ aContext->Restore();
+ }
+ currentClipIsSetInContext = clip->HasClip();
+ if (currentClipIsSetInContext) {
+ currentClip = *clip;
+ aContext->Save();
+ NS_ASSERTION(aCommonClipCount < 100,
+ "Maybe you really do have more than a hundred clipping rounded rects, or maybe something has gone wrong.");
+ currentClip.ApplyTo(aContext, aPresContext, aCommonClipCount);
+ aContext->NewPath();
+ }
+ }
+
+ if (cdi->mInactiveLayerManager) {
+ bool saved = aDrawTarget.GetPermitSubpixelAA();
+ PaintInactiveLayer(aBuilder, cdi->mInactiveLayerManager, cdi->mItem, aContext, aRC);
+ aDrawTarget.SetPermitSubpixelAA(saved);
+ } else {
+ nsIFrame* frame = cdi->mItem->Frame();
+ if (aBuilder->IsPaintingToWindow()) {
+ frame->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaintItems()) {
+ DebugPaintItem(aDrawTarget, aPresContext, cdi->mItem, aBuilder);
+ } else {
+#else
+ {
+#endif
+ cdi->mItem->Paint(aBuilder, aRC);
+ }
+ }
+
+ if (CheckDOMModified())
+ break;
+ }
+
+ if (currentClipIsSetInContext) {
+ aContext->Restore();
+ }
+}
+
+/**
+ * Returns true if it is preferred to draw the list of display
+ * items separately for each rect in the visible region rather
+ * than clipping to a complex region.
+ */
+static bool
+ShouldDrawRectsSeparately(DrawTarget* aDrawTarget, DrawRegionClip aClip)
+{
+ if (!gfxPrefs::LayoutPaintRectsSeparately() ||
+ aClip == DrawRegionClip::NONE) {
+ return false;
+ }
+
+ return !aDrawTarget->SupportsRegionClipping();
+}
+
+static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget,
+ const IntRect& aBounds,
+ nscolor aBackgroundColor)
+{
+ if (NS_GET_A(aBackgroundColor) > 0) {
+ ColorPattern color(ToDeviceColor(aBackgroundColor));
+ aDrawTarget.FillRect(Rect(aBounds), color);
+ }
+}
+
+/*
+ * A note on residual transforms:
+ *
+ * In a transformed subtree we sometimes apply the PaintedLayer's
+ * "residual transform" when drawing content into the PaintedLayer.
+ * This is a translation by components in the range [-0.5,0.5) provided
+ * by the layer system; applying the residual transform followed by the
+ * transforms used by layer compositing ensures that the subpixel alignment
+ * of the content of the PaintedLayer exactly matches what it would be if
+ * we used cairo/Thebes to draw directly to the screen without going through
+ * retained layer buffers.
+ *
+ * The visible and valid regions of the PaintedLayer are computed without
+ * knowing the residual transform (because we don't know what the residual
+ * transform is going to be until we've built the layer tree!). So we have to
+ * consider whether content painted in the range [x, xmost) might be painted
+ * outside the visible region we computed for that content. The visible region
+ * would be [floor(x), ceil(xmost)). The content would be rendered at
+ * [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could
+ * indeed fall outside the computed visible region, which is not a big deal;
+ * similar issues already arise when we snap cliprects to nearest pixels.
+ * Note that if the rendering of the content is snapped to nearest pixels ---
+ * which it often is --- then the content is actually rendered at
+ * [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r)
+ * and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content
+ * always falls within the visible region we computed.
+ */
+
+/* static */ void
+FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer,
+ gfxContext* aContext,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntRegion& aDirtyRegion,
+ DrawRegionClip aClip,
+ const nsIntRegion& aRegionToInvalidate,
+ void* aCallbackData)
+{
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ PROFILER_LABEL("FrameLayerBuilder", "DrawPaintedLayer",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ nsDisplayListBuilder* builder = static_cast<nsDisplayListBuilder*>
+ (aCallbackData);
+
+ FrameLayerBuilder *layerBuilder = aLayer->Manager()->GetLayerBuilder();
+ NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!");
+
+ if (layerBuilder->CheckDOMModified())
+ return;
+
+ PaintedLayerItemsEntry* entry = layerBuilder->mPaintedLayerItems.GetEntry(aLayer);
+ NS_ASSERTION(entry, "We shouldn't be drawing into a layer with no items!");
+ if (!entry->mContainerLayerFrame) {
+ return;
+ }
+
+
+ PaintedDisplayItemLayerUserData* userData =
+ static_cast<PaintedDisplayItemLayerUserData*>
+ (aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+ NS_ASSERTION(userData, "where did our user data go?");
+
+ bool shouldDrawRectsSeparately =
+ ShouldDrawRectsSeparately(&aDrawTarget, aClip);
+
+ if (!shouldDrawRectsSeparately) {
+ if (aClip == DrawRegionClip::DRAW) {
+ gfxUtils::ClipToRegion(aContext, aRegionToDraw);
+ }
+
+ DrawForcedBackgroundColor(aDrawTarget, aRegionToDraw.GetBounds(),
+ userData->mForcedBackgroundColor);
+ }
+
+ if (NS_GET_A(userData->mFontSmoothingBackgroundColor) > 0) {
+ aContext->SetFontSmoothingBackgroundColor(
+ Color::FromABGR(userData->mFontSmoothingBackgroundColor));
+ }
+
+ // make the origin of the context coincide with the origin of the
+ // PaintedLayer
+ gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
+ nsIntPoint offset = GetTranslationForPaintedLayer(aLayer);
+ nsPresContext* presContext = entry->mContainerLayerFrame->PresContext();
+
+ if (!userData->mVisibilityComputedRegion.Contains(aDirtyRegion) &&
+ !layerBuilder->GetContainingPaintedLayerData()) {
+ // Recompute visibility of items in our PaintedLayer, if required. Note
+ // that this recomputes visibility for all descendants of our display
+ // items too, so there's no need to do this for the items in inactive
+ // PaintedLayers. If aDirtyRegion has not changed since the previous call
+ // then we can skip this.
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ RecomputeVisibilityForItems(entry->mItems, builder, aDirtyRegion,
+ offset, appUnitsPerDevPixel,
+ userData->mXScale, userData->mYScale);
+ userData->mVisibilityComputedRegion = aDirtyRegion;
+ }
+
+ nsRenderingContext rc(aContext);
+
+ if (shouldDrawRectsSeparately) {
+ for (auto iter = aRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
+ const nsIntRect& iterRect = iter.Get();
+ gfxContextAutoSaveRestore save(aContext);
+ aContext->NewPath();
+ aContext->Rectangle(ThebesRect(iterRect));
+ aContext->Clip();
+
+ DrawForcedBackgroundColor(aDrawTarget, iterRect,
+ userData->mForcedBackgroundColor);
+
+ // Apply the residual transform if it has been enabled, to ensure that
+ // snapping when we draw into aContext exactly matches the ideal transform.
+ // See above for why this is OK.
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
+ Scale(userData->mXScale, userData->mYScale));
+
+ layerBuilder->PaintItems(entry->mItems, iterRect, aContext, &rc,
+ builder, presContext,
+ offset, userData->mXScale, userData->mYScale,
+ entry->mCommonClipCount);
+ if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) {
+ aLayer->Manager()->AddPaintedPixelCount(iterRect.Area());
+ }
+ }
+ } else {
+ // Apply the residual transform if it has been enabled, to ensure that
+ // snapping when we draw into aContext exactly matches the ideal transform.
+ // See above for why this is OK.
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
+ Scale(userData->mXScale,userData->mYScale));
+
+ layerBuilder->PaintItems(entry->mItems, aRegionToDraw.GetBounds(), aContext, &rc,
+ builder, presContext,
+ offset, userData->mXScale, userData->mYScale,
+ entry->mCommonClipCount);
+ if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) {
+ aLayer->Manager()->AddPaintedPixelCount(
+ aRegionToDraw.GetBounds().Area());
+ }
+ }
+
+ aContext->SetFontSmoothingBackgroundColor(Color());
+
+ bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager();
+
+ if (presContext->GetPaintFlashing() && isActiveLayerManager) {
+ gfxContextAutoSaveRestore save(aContext);
+ if (shouldDrawRectsSeparately) {
+ if (aClip == DrawRegionClip::DRAW) {
+ gfxUtils::ClipToRegion(aContext, aRegionToDraw);
+ }
+ }
+ FlashPaint(aContext);
+ }
+
+ if (presContext->GetDocShell() && isActiveLayerManager) {
+ nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+ if (timelines && timelines->HasConsumer(docShell)) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<LayerTimelineMarker>(aRegionToDraw)));
+ }
+ }
+
+ if (!aRegionToInvalidate.IsEmpty()) {
+ aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
+ }
+}
+
+bool
+FrameLayerBuilder::CheckDOMModified()
+{
+ if (!mRootPresContext ||
+ mInitialDOMGeneration == mRootPresContext->GetDOMGeneration())
+ return false;
+ if (mDetectedDOMModification) {
+ // Don't spam the console with extra warnings
+ return true;
+ }
+ mDetectedDOMModification = true;
+ // Painting is not going to complete properly. There's not much
+ // we can do here though. Invalidating the window to get another repaint
+ // is likely to lead to an infinite repaint loop.
+ NS_WARNING("Detected DOM modification during paint, bailing out!");
+ return true;
+}
+
+/* static */ void
+FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager, std::stringstream& aStream, bool aDumpHtml)
+{
+ aManager->Dump(aStream, "", aDumpHtml);
+}
+
+nsDisplayItemGeometry*
+FrameLayerBuilder::GetMostRecentGeometry(nsDisplayItem* aItem)
+{
+ typedef nsTArray<DisplayItemData*> DataArray;
+
+ // Retrieve the array of DisplayItemData associated with our frame.
+ FrameProperties properties = aItem->Frame()->Properties();
+ const DataArray* dataArray =
+ properties.Get(LayerManagerDataProperty());
+ if (!dataArray) {
+ return nullptr;
+ }
+
+ // Find our display item data, if it exists, and return its geometry.
+ uint32_t itemPerFrameKey = aItem->GetPerFrameKey();
+ for (uint32_t i = 0; i < dataArray->Length(); i++) {
+ DisplayItemData* data = AssertDisplayItemData(dataArray->ElementAt(i));
+ if (data->GetDisplayItemKey() == itemPerFrameKey) {
+ return data->GetGeometry();
+ }
+ }
+
+ return nullptr;
+}
+
+gfx::Rect
+CalculateBounds(const nsTArray<DisplayItemClip::RoundedRect>& aRects, int32_t A2D)
+{
+ nsRect bounds = aRects[0].mRect;
+ for (uint32_t i = 1; i < aRects.Length(); ++i) {
+ bounds.UnionRect(bounds, aRects[i].mRect);
+ }
+
+ return gfx::ToRect(nsLayoutUtils::RectToGfxRect(bounds, A2D));
+}
+
+static void
+SetClipCount(PaintedDisplayItemLayerUserData* apaintedData,
+ uint32_t aClipCount)
+{
+ if (apaintedData) {
+ apaintedData->mMaskClipCount = aClipCount;
+ }
+}
+
+void
+ContainerState::SetupMaskLayer(Layer *aLayer,
+ const DisplayItemClip& aClip,
+ uint32_t aRoundedRectClipCount)
+{
+ // if the number of clips we are going to mask has decreased, then aLayer might have
+ // cached graphics which assume the existence of a soon-to-be non-existent mask layer
+ // in that case, invalidate the whole layer.
+ PaintedDisplayItemLayerUserData* paintedData = GetPaintedDisplayItemLayerUserData(aLayer);
+ if (paintedData &&
+ aRoundedRectClipCount < paintedData->mMaskClipCount) {
+ PaintedLayer* painted = aLayer->AsPaintedLayer();
+ painted->InvalidateRegion(painted->GetValidRegion().GetBounds());
+ }
+
+ // don't build an unnecessary mask
+ if (aClip.GetRoundedRectCount() == 0 ||
+ aRoundedRectClipCount == 0) {
+ SetClipCount(paintedData, 0);
+ return;
+ }
+
+ RefPtr<Layer> maskLayer =
+ CreateMaskLayer(aLayer, aClip, Nothing(), aRoundedRectClipCount);
+
+ if (!maskLayer) {
+ SetClipCount(paintedData, 0);
+ return;
+ }
+
+ aLayer->SetMaskLayer(maskLayer);
+ SetClipCount(paintedData, aRoundedRectClipCount);
+}
+
+already_AddRefed<Layer>
+ContainerState::CreateMaskLayer(Layer *aLayer,
+ const DisplayItemClip& aClip,
+ const Maybe<size_t>& aForAncestorMaskLayer,
+ uint32_t aRoundedRectClipCount)
+{
+ // aLayer will never be the container layer created by an nsDisplayMask
+ // because nsDisplayMask propagates the DisplayItemClip to its contents
+ // and is not clipped itself.
+ // This assertion will fail if that ever stops being the case.
+ MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData),
+ "A layer contains round clips should not have css-mask on it.");
+
+ // check if we can re-use the mask layer
+ MaskLayerKey recycleKey(aLayer, aForAncestorMaskLayer);
+ RefPtr<ImageLayer> maskLayer =
+ CreateOrRecycleMaskImageLayerFor(recycleKey,
+ [](Layer* aMaskLayer)
+ {
+ aMaskLayer->SetUserData(&gMaskLayerUserData,
+ new MaskLayerUserData());
+ }
+ );
+ MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer);
+
+ int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
+ MaskLayerUserData newData(aClip, aRoundedRectClipCount, A2D, mParameters);
+ if (*userData == newData) {
+ return maskLayer.forget();
+ }
+
+ // calculate a more precise bounding rect
+ gfx::Rect boundingRect = CalculateBounds(newData.mRoundedClipRects,
+ newData.mAppUnitsPerDevPixel);
+ boundingRect.Scale(mParameters.mXScale, mParameters.mYScale);
+
+ uint32_t maxSize = mManager->GetMaxTextureSize();
+ NS_ASSERTION(maxSize > 0, "Invalid max texture size");
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ // Make mask image width aligned to 4. See Bug 1245552.
+ gfx::Size surfaceSize(std::min<gfx::Float>(GetAlignedStride<4>(NSToIntCeil(boundingRect.Width()), 1), maxSize),
+ std::min<gfx::Float>(boundingRect.Height(), maxSize));
+#else
+ gfx::Size surfaceSize(std::min<gfx::Float>(boundingRect.Width(), maxSize),
+ std::min<gfx::Float>(boundingRect.Height(), maxSize));
+#endif
+
+ // maskTransform is applied to the clip when it is painted into the mask (as a
+ // component of imageTransform), and its inverse used when the mask is used for
+ // masking.
+ // It is the transform from the masked layer's space to mask space
+ gfx::Matrix maskTransform =
+ Matrix::Scaling(surfaceSize.width / boundingRect.Width(),
+ surfaceSize.height / boundingRect.Height());
+ if (surfaceSize.IsEmpty()) {
+ // Return early if we know that the size of this mask surface is empty.
+ return nullptr;
+ }
+
+ gfx::Point p = boundingRect.TopLeft();
+ maskTransform.PreTranslate(-p.x, -p.y);
+ // imageTransform is only used when the clip is painted to the mask
+ gfx::Matrix imageTransform = maskTransform;
+ imageTransform.PreScale(mParameters.mXScale, mParameters.mYScale);
+
+ nsAutoPtr<MaskLayerImageCache::MaskLayerImageKey> newKey(
+ new MaskLayerImageCache::MaskLayerImageKey());
+
+ // copy and transform the rounded rects
+ for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) {
+ newKey->mRoundedClipRects.AppendElement(
+ MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i],
+ mContainerFrame->PresContext()));
+ newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform);
+ }
+ newKey->mForwarder = mManager->AsShadowForwarder();
+
+ const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey;
+
+ // check to see if we can reuse a mask image
+ RefPtr<ImageContainer> container =
+ GetMaskLayerImageCache()->FindImageFor(&lookupKey);
+
+ if (!container) {
+ IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width),
+ NSToIntCeil(surfaceSize.height));
+ // no existing mask image, so build a new one
+ MaskImageData imageData(surfaceSizeInt, mManager);
+ RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
+
+ // fail if we can't get the right surface
+ if (!dt || !dt->IsValid()) {
+ NS_WARNING("Could not create DrawTarget for mask layer.");
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(context); // already checked the draw target above
+ context->Multiply(ThebesMatrix(imageTransform));
+
+ // paint the clipping rects with alpha to create the mask
+ aClip.FillIntersectionOfRoundedRectClips(context,
+ Color(1.f, 1.f, 1.f, 1.f),
+ newData.mAppUnitsPerDevPixel,
+ 0,
+ aRoundedRectClipCount);
+
+ // build the image and container
+ MOZ_ASSERT(aLayer->Manager() == mManager);
+ container = imageData.CreateImageAndImageContainer();
+ NS_ASSERTION(container, "Could not create image container for mask layer.");
+
+ if (!container) {
+ return nullptr;
+ }
+
+ GetMaskLayerImageCache()->PutImage(newKey.forget(), container);
+ }
+
+ maskLayer->SetContainer(container);
+
+ maskTransform.Invert();
+ Matrix4x4 matrix = Matrix4x4::From2D(maskTransform);
+ matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
+ maskLayer->SetBaseTransform(matrix);
+
+ // save the details of the clip in user data
+ *userData = Move(newData);
+ userData->mImageKey.Reset(lookupKey);
+
+ return maskLayer.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/base/FrameLayerBuilder.h b/layout/base/FrameLayerBuilder.h
new file mode 100644
index 000000000..f2b58aa75
--- /dev/null
+++ b/layout/base/FrameLayerBuilder.h
@@ -0,0 +1,737 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FRAMELAYERBUILDER_H_
+#define FRAMELAYERBUILDER_H_
+
+#include "nsAutoPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsRegion.h"
+#include "nsIFrame.h"
+#include "DisplayItemClip.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "LayerState.h"
+#include "Layers.h"
+#include "LayerUserData.h"
+
+class nsDisplayListBuilder;
+class nsDisplayList;
+class nsDisplayItem;
+class gfxContext;
+class nsDisplayItemGeometry;
+class nsDisplayMask;
+
+namespace mozilla {
+class DisplayItemScrollClip;
+namespace layers {
+class ContainerLayer;
+class LayerManager;
+class BasicLayerManager;
+class PaintedLayer;
+class ImageLayer;
+} // namespace layers
+
+class FrameLayerBuilder;
+class LayerManagerData;
+class PaintedLayerData;
+class ContainerState;
+
+class RefCountedRegion {
+private:
+ ~RefCountedRegion() {}
+public:
+ NS_INLINE_DECL_REFCOUNTING(RefCountedRegion)
+
+ RefCountedRegion() : mIsInfinite(false) {}
+ nsRegion mRegion;
+ bool mIsInfinite;
+};
+
+struct ContainerLayerParameters {
+ ContainerLayerParameters()
+ : mXScale(1)
+ , mYScale(1)
+ , mLayerContentsVisibleRect(nullptr)
+ , mBackgroundColor(NS_RGBA(0,0,0,0))
+ , mScrollClip(nullptr)
+ , mScrollClipForPerspectiveChild(nullptr)
+ , mInTransformedSubtree(false)
+ , mInActiveTransformedSubtree(false)
+ , mDisableSubpixelAntialiasingInDescendants(false)
+ , mInLowPrecisionDisplayPort(false)
+ , mForEventsAndPluginsOnly(false)
+ , mLayerCreationHint(layers::LayerManager::NONE)
+ {}
+ ContainerLayerParameters(float aXScale, float aYScale)
+ : mXScale(aXScale)
+ , mYScale(aYScale)
+ , mLayerContentsVisibleRect(nullptr)
+ , mBackgroundColor(NS_RGBA(0,0,0,0))
+ , mScrollClip(nullptr)
+ , mScrollClipForPerspectiveChild(nullptr)
+ , mInTransformedSubtree(false)
+ , mInActiveTransformedSubtree(false)
+ , mDisableSubpixelAntialiasingInDescendants(false)
+ , mInLowPrecisionDisplayPort(false)
+ , mForEventsAndPluginsOnly(false)
+ , mLayerCreationHint(layers::LayerManager::NONE)
+ {}
+ ContainerLayerParameters(float aXScale, float aYScale,
+ const nsIntPoint& aOffset,
+ const ContainerLayerParameters& aParent)
+ : mXScale(aXScale)
+ , mYScale(aYScale)
+ , mLayerContentsVisibleRect(nullptr)
+ , mOffset(aOffset)
+ , mBackgroundColor(aParent.mBackgroundColor)
+ , mScrollClip(aParent.mScrollClip)
+ , mScrollClipForPerspectiveChild(aParent.mScrollClipForPerspectiveChild)
+ , mInTransformedSubtree(aParent.mInTransformedSubtree)
+ , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree)
+ , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
+ , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort)
+ , mForEventsAndPluginsOnly(aParent.mForEventsAndPluginsOnly)
+ , mLayerCreationHint(aParent.mLayerCreationHint)
+ {}
+
+ float mXScale, mYScale;
+
+ LayoutDeviceToLayerScale2D Scale() const {
+ return LayoutDeviceToLayerScale2D(mXScale, mYScale);
+ }
+
+ /**
+ * If non-null, the rectangle in which BuildContainerLayerFor stores the
+ * visible rect of the layer, in the coordinate system of the created layer.
+ */
+ nsIntRect* mLayerContentsVisibleRect;
+
+ /**
+ * An offset to apply to all child layers created.
+ */
+ nsIntPoint mOffset;
+
+ LayerIntPoint Offset() const {
+ return LayerIntPoint::FromUnknownPoint(mOffset);
+ }
+
+ nscolor mBackgroundColor;
+ const DisplayItemScrollClip* mScrollClip;
+
+ // usually nullptr, except when building children of an nsDisplayPerspective
+ const DisplayItemScrollClip* mScrollClipForPerspectiveChild;
+
+ bool mInTransformedSubtree;
+ bool mInActiveTransformedSubtree;
+ bool mDisableSubpixelAntialiasingInDescendants;
+ bool mInLowPrecisionDisplayPort;
+ bool mForEventsAndPluginsOnly;
+ layers::LayerManager::PaintedLayerCreationHint mLayerCreationHint;
+
+ /**
+ * When this is false, PaintedLayer coordinates are drawn to with an integer
+ * translation and the scale in mXScale/mYScale.
+ */
+ bool AllowResidualTranslation()
+ {
+ // If we're in a transformed subtree, but no ancestor transform is actively
+ // changing, we'll use the residual translation when drawing into the
+ // PaintedLayer to ensure that snapping exactly matches the ideal transform.
+ return mInTransformedSubtree && !mInActiveTransformedSubtree;
+ }
+};
+
+/**
+ * The FrameLayerBuilder is responsible for converting display lists
+ * into layer trees. Every LayerManager needs a unique FrameLayerBuilder
+ * to build layers.
+ *
+ * The most important API in this class is BuildContainerLayerFor. This
+ * method takes a display list as input and constructs a ContainerLayer
+ * with child layers that render the contents of the display list. It
+ * records the relationship between frames and layers.
+ *
+ * That data enables us to retain layer trees. When constructing a
+ * ContainerLayer, we first check to see if there's an existing
+ * ContainerLayer for the same frame that can be recycled. If we recycle
+ * it, we also try to reuse its existing PaintedLayer children to render
+ * the display items without layers of their own. The idea is that by
+ * recycling layers deterministically, we can ensure that when nothing
+ * changes in a display list, we will reuse the existing layers without
+ * changes.
+ *
+ * We expose a GetLeafLayerFor method that can be called by display items
+ * that make their own layers (e.g. canvas and video); this method
+ * locates the last layer used to render the display item, if any, and
+ * return it as a candidate for recycling.
+ *
+ * FrameLayerBuilder sets up PaintedLayers so that 0,0 in the Painted layer
+ * corresponds to the (pixel-snapped) top-left of the aAnimatedGeometryRoot.
+ * It sets up ContainerLayers so that 0,0 in the container layer
+ * corresponds to the snapped top-left of the display item reference frame.
+ *
+ * When we construct a container layer, we know the transform that will be
+ * applied to the layer. If the transform scales the content, we can get
+ * better results when intermediate buffers are used by pushing some scale
+ * from the container's transform down to the children. For PaintedLayer
+ * children, the scaling can be achieved by changing the size of the layer
+ * and drawing into it with increased or decreased resolution. By convention,
+ * integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer
+ * coordinates, post-scaling, whereas appunit types are all pre-scaling.
+ */
+class FrameLayerBuilder : public layers::LayerUserData {
+public:
+ typedef layers::ContainerLayer ContainerLayer;
+ typedef layers::Layer Layer;
+ typedef layers::PaintedLayer PaintedLayer;
+ typedef layers::ImageLayer ImageLayer;
+ typedef layers::LayerManager LayerManager;
+ typedef layers::BasicLayerManager BasicLayerManager;
+ typedef layers::EventRegions EventRegions;
+
+ FrameLayerBuilder();
+ ~FrameLayerBuilder();
+
+ static void Shutdown();
+
+ void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
+ PaintedLayerData* aLayerData = nullptr);
+
+ /**
+ * Call this to notify that we have just started a transaction on the
+ * retained layer manager aManager.
+ */
+ void DidBeginRetainedLayerTransaction(LayerManager* aManager);
+
+ /**
+ * Call this just before we end a transaction.
+ */
+ void WillEndTransaction();
+
+ /**
+ * Call this after we end a transaction.
+ */
+ void DidEndTransaction();
+
+ enum {
+ /**
+ * Set this when pulling an opaque background color from behind the
+ * container layer into the container doesn't change the visual results,
+ * given the effects you're going to apply to the container layer.
+ * For example, this is compatible with opacity or clipping/masking, but
+ * not with non-OVER blend modes or filters.
+ */
+ CONTAINER_ALLOW_PULL_BACKGROUND_COLOR = 0x01
+ };
+ /**
+ * Build a container layer for a display item that contains a child
+ * list, either reusing an existing one or creating a new one. It
+ * sets the container layer children to layers which together render
+ * the contents of the display list. It reuses existing layers from
+ * the retained layer manager if possible.
+ * aContainerItem may be null, in which case we construct a root layer.
+ * This gets called by display list code. It calls BuildLayer on the
+ * items in the display list, making items with their own layers
+ * children of the new container, and assigning all other items to
+ * PaintedLayer children created and managed by the FrameLayerBuilder.
+ * Returns a layer with clip rect cleared; it is the
+ * caller's responsibility to add any clip rect. The visible region
+ * is set based on what's in the layer.
+ * The container layer is transformed by aTransform (if non-null), and
+ * the result is transformed by the scale factors in aContainerParameters.
+ * aChildren is modified due to display item merging and flattening.
+ * The visible region of the returned layer is set only if aContainerItem
+ * is null.
+ */
+ already_AddRefed<ContainerLayer>
+ BuildContainerLayerFor(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ nsDisplayList* aChildren,
+ const ContainerLayerParameters& aContainerParameters,
+ const gfx::Matrix4x4* aTransform,
+ uint32_t aFlags = 0);
+
+ /**
+ * Get a retained layer for a display item that needs to create its own
+ * layer for rendering (i.e. under nsDisplayItem::BuildLayer). Returns
+ * null if no retained layer is available, which usually means that this
+ * display item didn't have a layer before so the caller will
+ * need to create one.
+ * Returns a layer with clip rect cleared; it is the
+ * caller's responsibility to add any clip rect and set the visible
+ * region.
+ */
+ Layer* GetLeafLayerFor(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem);
+
+ /**
+ * Call this to force all retained layers to be discarded and recreated at
+ * the next paint.
+ */
+ static void InvalidateAllLayers(LayerManager* aManager);
+ static void InvalidateAllLayersForFrame(nsIFrame *aFrame);
+
+ /**
+ * Call this to determine if a frame has a dedicated (non-Painted) layer
+ * for the given display item key. If there isn't one, we return null,
+ * otherwise we return the layer.
+ */
+ static Layer* GetDedicatedLayer(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+
+ /**
+ * This callback must be provided to EndTransaction. The callback data
+ * must be the nsDisplayListBuilder containing this FrameLayerBuilder.
+ * This function can be called multiple times in a row to draw
+ * different regions. This will occur when, for example, progressive paint is
+ * enabled. In these cases aDirtyRegion can be used to specify a larger region
+ * than aRegionToDraw that will be drawn during the transaction, possibly
+ * allowing the callback to make optimizations.
+ */
+ static void DrawPaintedLayer(PaintedLayer* aLayer,
+ gfxContext* aContext,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntRegion& aDirtyRegion,
+ mozilla::layers::DrawRegionClip aClip,
+ const nsIntRegion& aRegionToInvalidate,
+ void* aCallbackData);
+
+ /**
+ * Dumps this FrameLayerBuilder's retained layer manager's retained
+ * layer tree. Defaults to dumping to stdout in non-HTML format.
+ */
+ static void DumpRetainedLayerTree(LayerManager* aManager, std::stringstream& aStream, bool aDumpHtml = false);
+
+ /**
+ * Returns the most recently allocated geometry item for the given display
+ * item.
+ *
+ * XXX(seth): The current implementation must iterate through all display
+ * items allocated for this display item's frame. This may lead to O(n^2)
+ * behavior in some situations.
+ */
+ static nsDisplayItemGeometry* GetMostRecentGeometry(nsDisplayItem* aItem);
+
+
+ /******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/
+ /* These are only in the public section because they need
+ * to be called by file-scope helper functions in FrameLayerBuilder.cpp.
+ */
+
+ /**
+ * Record aItem as a display item that is rendered by aLayer.
+ *
+ * @param aLayer Layer that the display item will be rendered into
+ * @param aItem Display item to be drawn.
+ * @param aLayerState What LayerState the item is using.
+ * @param aManager If the layer is in the LAYER_INACTIVE state,
+ * then this is the temporary layer manager to draw with.
+ */
+ void AddLayerDisplayItem(Layer* aLayer,
+ nsDisplayItem* aItem,
+ LayerState aLayerState,
+ BasicLayerManager* aManager);
+
+ /**
+ * Record aItem as a display item that is rendered by the PaintedLayer
+ * aLayer, with aClipRect, where aContainerLayerFrame is the frame
+ * for the container layer this ThebesItem belongs to.
+ * aItem must have an underlying frame.
+ * @param aTopLeft offset from active scrolled root to reference frame
+ */
+ void AddPaintedDisplayItem(PaintedLayerData* aLayer,
+ nsDisplayItem* aItem,
+ const DisplayItemClip& aClip,
+ ContainerState& aContainerState,
+ LayerState aLayerState,
+ const nsPoint& aTopLeft);
+
+ /**
+ * Calls GetOldLayerForFrame on the underlying frame of the display item,
+ * and each subsequent merged frame if no layer is found for the underlying
+ * frame.
+ */
+ Layer* GetOldLayerFor(nsDisplayItem* aItem,
+ nsDisplayItemGeometry** aOldGeometry = nullptr,
+ DisplayItemClip** aOldClip = nullptr);
+
+ void ClearCachedGeometry(nsDisplayItem* aItem);
+
+ static Layer* GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+
+ /**
+ * Return the layer that all display items of aFrame were assigned to in the
+ * last paint, or nullptr if there was no single layer assigned to all of the
+ * frame's display items (i.e. zero, or more than one).
+ * This function is for testing purposes and not performance sensitive.
+ */
+ static PaintedLayer* GetDebugSingleOldPaintedLayerForFrame(nsIFrame* aFrame);
+
+ /**
+ * Destroy any stored LayerManagerDataProperty and the associated data for
+ * aFrame.
+ */
+ static void DestroyDisplayItemDataFor(nsIFrame* aFrame);
+
+ LayerManager* GetRetainingLayerManager() { return mRetainingManager; }
+
+ /**
+ * Returns true if the given display item was rendered during the previous
+ * paint. Returns false otherwise.
+ */
+ static bool HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+
+ class DisplayItemData;
+ typedef void (*DisplayItemDataCallback)(nsIFrame *aFrame, DisplayItemData* aItem);
+
+ static void IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback);
+
+ /**
+ * Save transform that was in aLayer when we last painted, and the position
+ * of the active scrolled root frame. It must be an integer
+ * translation.
+ */
+ void SavePreviousDataForLayer(PaintedLayer* aLayer, uint32_t aClipCount);
+ /**
+ * Get the translation transform that was in aLayer when we last painted. It's either
+ * the transform saved by SaveLastPaintTransform, or else the transform
+ * that's currently in the layer (which must be an integer translation).
+ */
+ nsIntPoint GetLastPaintOffset(PaintedLayer* aLayer);
+
+ /**
+ * Return the resolution at which we expect to render aFrame's contents,
+ * assuming they are being painted to retained layers. This takes into account
+ * the resolution the contents of the ContainerLayer containing aFrame are
+ * being rendered at, as well as any currently-inactive transforms between
+ * aFrame and that container layer.
+ */
+ static gfxSize GetPaintedLayerScaleForFrame(nsIFrame* aFrame);
+
+ /**
+ * Stores a Layer as the dedicated layer in the DisplayItemData for a given frame/key pair.
+ *
+ * Used when we optimize a PaintedLayer into an ImageLayer and want to retroactively update the
+ * DisplayItemData so we can retrieve the layer from within layout.
+ */
+ void StoreOptimizedLayerForFrame(nsDisplayItem* aItem, Layer* aLayer);
+
+ NS_DECLARE_FRAME_PROPERTY_WITH_FRAME_IN_DTOR(LayerManagerDataProperty,
+ nsTArray<DisplayItemData*>,
+ RemoveFrameFromLayerManager)
+
+ /**
+ * Retained data storage:
+ *
+ * Each layer manager (widget, and inactive) stores a LayerManagerData object
+ * that keeps a hash-set of DisplayItemData items that were drawn into it.
+ * Each frame also keeps a list of DisplayItemData pointers that were
+ * created for that frame. DisplayItemData objects manage these lists automatically.
+ *
+ * During layer construction we update the data in the LayerManagerData object, marking
+ * items that are modified. At the end we sweep the LayerManagerData hash-set and remove
+ * all items that haven't been modified.
+ */
+
+ /**
+ * Retained data for a display item.
+ */
+ class DisplayItemData final {
+ public:
+ friend class FrameLayerBuilder;
+
+ uint32_t GetDisplayItemKey() { return mDisplayItemKey; }
+ Layer* GetLayer() { return mLayer; }
+ nsDisplayItemGeometry* GetGeometry() const { return mGeometry.get(); }
+ void Invalidate() { mIsInvalid = true; }
+ void ClearAnimationCompositorState();
+
+ private:
+ DisplayItemData(LayerManagerData* aParent,
+ uint32_t aKey,
+ Layer* aLayer,
+ nsIFrame* aFrame = nullptr);
+
+ /**
+ * Removes any references to this object from frames
+ * in mFrameList.
+ */
+ ~DisplayItemData();
+
+ NS_INLINE_DECL_REFCOUNTING(DisplayItemData)
+
+
+ /**
+ * Associates this DisplayItemData with a frame, and adds it
+ * to the LayerManagerDataProperty list on the frame.
+ */
+ void AddFrame(nsIFrame* aFrame);
+ void RemoveFrame(nsIFrame* aFrame);
+ const nsTArray<nsIFrame*>& GetFrameListChanges();
+
+ /**
+ * Updates the contents of this item to a new set of data, instead of allocating a new
+ * object.
+ * Set the passed in parameters, and clears the opt layer and inactive manager.
+ * Parent, and display item key are assumed to be the same.
+ *
+ * EndUpdate must be called before the end of the transaction to complete the update.
+ */
+ void BeginUpdate(Layer* aLayer, LayerState aState,
+ uint32_t aContainerLayerGeneration, nsDisplayItem* aItem = nullptr);
+
+ /**
+ * Completes the update of this, and removes any references to data that won't live
+ * longer than the transaction.
+ *
+ * Updates the geometry, frame list and clip.
+ * For items within a PaintedLayer, a geometry object must be specified to retain
+ * until the next transaction.
+ *
+ */
+ void EndUpdate(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
+ void EndUpdate();
+
+ LayerManagerData* mParent;
+ RefPtr<Layer> mLayer;
+ RefPtr<Layer> mOptLayer;
+ RefPtr<BasicLayerManager> mInactiveManager;
+ AutoTArray<nsIFrame*, 1> mFrameList;
+ nsAutoPtr<nsDisplayItemGeometry> mGeometry;
+ DisplayItemClip mClip;
+ uint32_t mDisplayItemKey;
+ uint32_t mContainerLayerGeneration;
+ LayerState mLayerState;
+
+ /**
+ * Temporary stoarage of the display item being referenced, only valid between
+ * BeginUpdate and EndUpdate.
+ */
+ nsDisplayItem* mItem;
+ AutoTArray<nsIFrame*, 1> mFrameListChanges;
+
+ /**
+ * Used to track if data currently stored in mFramesWithLayers (from an existing
+ * paint) has been updated in the current paint.
+ */
+ bool mUsed;
+ bool mIsInvalid;
+ };
+
+protected:
+
+ friend class LayerManagerData;
+
+ static void RemoveFrameFromLayerManager(const nsIFrame* aFrame,
+ nsTArray<DisplayItemData*>* aArray);
+
+ /**
+ * Given a frame and a display item key that uniquely identifies a
+ * display item for the frame, find the layer that was last used to
+ * render that display item. Returns null if there is no such layer.
+ * This could be a dedicated layer for the display item, or a PaintedLayer
+ * that renders many display items.
+ */
+ DisplayItemData* GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+
+ /**
+ * Stores DisplayItemData associated with aFrame, stores the data in
+ * mNewDisplayItemData.
+ */
+ DisplayItemData* StoreDataForFrame(nsDisplayItem* aItem, Layer* aLayer, LayerState aState);
+ void StoreDataForFrame(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey,
+ Layer* aLayer,
+ LayerState aState);
+
+ // Flash the area within the context clip if paint flashing is enabled.
+ static void FlashPaint(gfxContext *aContext);
+
+ /*
+ * Get the DisplayItemData array associated with this frame, or null if one
+ * doesn't exist.
+ *
+ * Note that the pointer returned here is only valid so long as you don't
+ * poke the LayerManagerData's mFramesWithLayers hashtable.
+ */
+ DisplayItemData* GetDisplayItemData(nsIFrame *aFrame, uint32_t aKey);
+
+ /*
+ * Get the DisplayItemData associated with this frame / display item pair,
+ * using the LayerManager instead of FrameLayerBuilder.
+ */
+ static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey,
+ LayerManager* aManager);
+ static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey);
+ static DisplayItemData* GetDisplayItemDataForManager(nsDisplayItem* aItem, LayerManager* aManager);
+ static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
+ uint32_t aDisplayItemKey,
+ LayerManagerData* aData);
+
+ /**
+ * We store one of these for each display item associated with a
+ * PaintedLayer, in a hashtable that maps each PaintedLayer to an array
+ * of ClippedDisplayItems. (PaintedLayerItemsEntry is the hash entry
+ * for that hashtable.)
+ * These are only stored during the paint process, so that the
+ * DrawPaintedLayer callback can figure out which items to draw for the
+ * PaintedLayer.
+ */
+ struct ClippedDisplayItem {
+ ClippedDisplayItem(nsDisplayItem* aItem, uint32_t aGeneration);
+ ~ClippedDisplayItem();
+
+ nsDisplayItem* mItem;
+
+ /**
+ * If the display item is being rendered as an inactive
+ * layer, then this stores the layer manager being
+ * used for the inactive transaction.
+ */
+ RefPtr<LayerManager> mInactiveLayerManager;
+
+ uint32_t mContainerLayerGeneration;
+
+ };
+
+ static void RecomputeVisibilityForItems(nsTArray<ClippedDisplayItem>& aItems,
+ nsDisplayListBuilder* aBuilder,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntPoint& aOffset,
+ int32_t aAppUnitsPerDevPixel,
+ float aXScale,
+ float aYScale);
+
+ void PaintItems(nsTArray<ClippedDisplayItem>& aItems,
+ const nsIntRect& aRect,
+ gfxContext* aContext,
+ nsRenderingContext* aRC,
+ nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ const nsIntPoint& aOffset,
+ float aXScale, float aYScale,
+ int32_t aCommonClipCount);
+
+ /**
+ * We accumulate ClippedDisplayItem elements in a hashtable during
+ * the paint process. This is the hashentry for that hashtable.
+ */
+public:
+ class PaintedLayerItemsEntry : public nsPtrHashKey<PaintedLayer> {
+ public:
+ explicit PaintedLayerItemsEntry(const PaintedLayer *key);
+ PaintedLayerItemsEntry(const PaintedLayerItemsEntry&);
+ ~PaintedLayerItemsEntry();
+
+ nsTArray<ClippedDisplayItem> mItems;
+ nsIFrame* mContainerLayerFrame;
+ // The translation set on this PaintedLayer before we started updating the
+ // layer tree.
+ nsIntPoint mLastPaintOffset;
+ uint32_t mLastCommonClipCount;
+
+ uint32_t mContainerLayerGeneration;
+ bool mHasExplicitLastPaintOffset;
+ /**
+ * The first mCommonClipCount rounded rectangle clips are identical for
+ * all items in the layer. Computed in PaintedLayerData.
+ */
+ uint32_t mCommonClipCount;
+
+ enum { ALLOW_MEMMOVE = true };
+ };
+
+ /**
+ * Get the PaintedLayerItemsEntry object associated with aLayer in this
+ * FrameLayerBuilder
+ */
+ PaintedLayerItemsEntry* GetPaintedLayerItemsEntry(PaintedLayer* aLayer)
+ {
+ return mPaintedLayerItems.GetEntry(aLayer);
+ }
+
+ PaintedLayerData* GetContainingPaintedLayerData()
+ {
+ return mContainingPaintedLayer;
+ }
+
+ bool IsBuildingRetainedLayers()
+ {
+ return !mContainingPaintedLayer && mRetainingManager;
+ }
+
+ /**
+ * Attempt to build the most compressed layer tree possible, even if it means
+ * throwing away existing retained buffers.
+ */
+ void SetLayerTreeCompressionMode() { mInLayerTreeCompressionMode = true; }
+ bool CheckInLayerTreeCompressionMode();
+
+ void ComputeGeometryChangeForItem(DisplayItemData* aData);
+
+protected:
+ /**
+ * Returns true if the DOM has been modified since we started painting,
+ * in which case we should bail out and not paint anymore. This should
+ * never happen, but plugins can trigger it in some cases.
+ */
+ bool CheckDOMModified();
+
+ /**
+ * The layer manager belonging to the widget that is being retained
+ * across paints.
+ */
+ LayerManager* mRetainingManager;
+ /**
+ * The root prescontext for the display list builder reference frame
+ */
+ RefPtr<nsRootPresContext> mRootPresContext;
+
+ /**
+ * The display list builder being used.
+ */
+ nsDisplayListBuilder* mDisplayListBuilder;
+ /**
+ * A map from PaintedLayers to the list of display items (plus
+ * clipping data) to be rendered in the layer.
+ */
+ nsTHashtable<PaintedLayerItemsEntry> mPaintedLayerItems;
+
+ /**
+ * When building layers for an inactive layer, this is where the
+ * inactive layer will be placed.
+ */
+ PaintedLayerData* mContainingPaintedLayer;
+
+ /**
+ * Saved generation counter so we can detect DOM changes.
+ */
+ uint32_t mInitialDOMGeneration;
+ /**
+ * Set to true if we have detected and reported DOM modification during
+ * the current paint.
+ */
+ bool mDetectedDOMModification;
+ /**
+ * Indicates that the entire layer tree should be rerendered
+ * during this paint.
+ */
+ bool mInvalidateAllLayers;
+
+ bool mInLayerTreeCompressionMode;
+
+ uint32_t mContainerLayerGeneration;
+ uint32_t mMaxContainerLayerGeneration;
+};
+
+} // namespace mozilla
+
+#endif /* FRAMELAYERBUILDER_H_ */
diff --git a/layout/base/FramePropertyTable.cpp b/layout/base/FramePropertyTable.cpp
new file mode 100644
index 000000000..0fd9b1c37
--- /dev/null
+++ b/layout/base/FramePropertyTable.cpp
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FramePropertyTable.h"
+
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+void
+FramePropertyTable::SetInternal(
+ const nsIFrame* aFrame, UntypedDescriptor aProperty, void* aValue)
+{
+ NS_ASSERTION(aFrame, "Null frame?");
+ NS_ASSERTION(aProperty, "Null property?");
+
+ if (mLastFrame != aFrame || !mLastEntry) {
+ mLastFrame = aFrame;
+ mLastEntry = mEntries.PutEntry(aFrame);
+ }
+ Entry* entry = mLastEntry;
+
+ if (!entry->mProp.IsArray()) {
+ if (!entry->mProp.mProperty) {
+ // Empty entry, so we can just store our property in the empty slot
+ entry->mProp.mProperty = aProperty;
+ entry->mProp.mValue = aValue;
+ return;
+ }
+ if (entry->mProp.mProperty == aProperty) {
+ // Just overwrite the current value
+ entry->mProp.DestroyValueFor(aFrame);
+ entry->mProp.mValue = aValue;
+ return;
+ }
+
+ // We need to expand the single current entry to an array
+ PropertyValue current = entry->mProp;
+ entry->mProp.mProperty = nullptr;
+ static_assert(sizeof(nsTArray<PropertyValue>) <= sizeof(void *),
+ "Property array must fit entirely within entry->mProp.mValue");
+ new (&entry->mProp.mValue) nsTArray<PropertyValue>(4);
+ entry->mProp.ToArray()->AppendElement(current);
+ }
+
+ nsTArray<PropertyValue>* array = entry->mProp.ToArray();
+ nsTArray<PropertyValue>::index_type index =
+ array->IndexOf(aProperty, 0, PropertyComparator());
+ if (index != nsTArray<PropertyValue>::NoIndex) {
+ PropertyValue* pv = &array->ElementAt(index);
+ pv->DestroyValueFor(aFrame);
+ pv->mValue = aValue;
+ return;
+ }
+
+ array->AppendElement(PropertyValue(aProperty, aValue));
+}
+
+void*
+FramePropertyTable::GetInternal(
+ const nsIFrame* aFrame, UntypedDescriptor aProperty, bool* aFoundResult)
+{
+ NS_ASSERTION(aFrame, "Null frame?");
+ NS_ASSERTION(aProperty, "Null property?");
+
+ if (aFoundResult) {
+ *aFoundResult = false;
+ }
+
+ if (mLastFrame != aFrame) {
+ mLastFrame = aFrame;
+ mLastEntry = mEntries.GetEntry(mLastFrame);
+ }
+ Entry* entry = mLastEntry;
+ if (!entry)
+ return nullptr;
+
+ if (entry->mProp.mProperty == aProperty) {
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+ return entry->mProp.mValue;
+ }
+ if (!entry->mProp.IsArray()) {
+ // There's just one property and it's not the one we want, bail
+ return nullptr;
+ }
+
+ nsTArray<PropertyValue>* array = entry->mProp.ToArray();
+ nsTArray<PropertyValue>::index_type index =
+ array->IndexOf(aProperty, 0, PropertyComparator());
+ if (index == nsTArray<PropertyValue>::NoIndex)
+ return nullptr;
+
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+
+ return array->ElementAt(index).mValue;
+}
+
+void*
+FramePropertyTable::RemoveInternal(
+ const nsIFrame* aFrame, UntypedDescriptor aProperty, bool* aFoundResult)
+{
+ NS_ASSERTION(aFrame, "Null frame?");
+ NS_ASSERTION(aProperty, "Null property?");
+
+ if (aFoundResult) {
+ *aFoundResult = false;
+ }
+
+ if (mLastFrame != aFrame) {
+ mLastFrame = aFrame;
+ mLastEntry = mEntries.GetEntry(aFrame);
+ }
+ Entry* entry = mLastEntry;
+ if (!entry)
+ return nullptr;
+
+ if (entry->mProp.mProperty == aProperty) {
+ // There's only one entry and it's the one we want
+ void* value = entry->mProp.mValue;
+
+ // Here it's ok to use RemoveEntry() -- which may resize mEntries --
+ // because we null mLastEntry at the same time.
+ mEntries.RemoveEntry(entry);
+ mLastEntry = nullptr;
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+ return value;
+ }
+ if (!entry->mProp.IsArray()) {
+ // There's just one property and it's not the one we want, bail
+ return nullptr;
+ }
+
+ nsTArray<PropertyValue>* array = entry->mProp.ToArray();
+ nsTArray<PropertyValue>::index_type index =
+ array->IndexOf(aProperty, 0, PropertyComparator());
+ if (index == nsTArray<PropertyValue>::NoIndex) {
+ // No such property, bail
+ return nullptr;
+ }
+
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+
+ void* result = array->ElementAt(index).mValue;
+
+ uint32_t last = array->Length() - 1;
+ array->ElementAt(index) = array->ElementAt(last);
+ array->RemoveElementAt(last);
+
+ if (last == 1) {
+ PropertyValue pv = array->ElementAt(0);
+ array->~nsTArray<PropertyValue>();
+ entry->mProp = pv;
+ }
+
+ return result;
+}
+
+void
+FramePropertyTable::DeleteInternal(
+ const nsIFrame* aFrame, UntypedDescriptor aProperty)
+{
+ NS_ASSERTION(aFrame, "Null frame?");
+ NS_ASSERTION(aProperty, "Null property?");
+
+ bool found;
+ void* v = RemoveInternal(aFrame, aProperty, &found);
+ if (found) {
+ PropertyValue pv(aProperty, v);
+ pv.DestroyValueFor(aFrame);
+ }
+}
+
+/* static */ void
+FramePropertyTable::DeleteAllForEntry(Entry* aEntry)
+{
+ if (!aEntry->mProp.IsArray()) {
+ aEntry->mProp.DestroyValueFor(aEntry->GetKey());
+ return;
+ }
+
+ nsTArray<PropertyValue>* array = aEntry->mProp.ToArray();
+ for (uint32_t i = 0; i < array->Length(); ++i) {
+ array->ElementAt(i).DestroyValueFor(aEntry->GetKey());
+ }
+ array->~nsTArray<PropertyValue>();
+}
+
+void
+FramePropertyTable::DeleteAllFor(const nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "Null frame?");
+
+ Entry* entry = mEntries.GetEntry(aFrame);
+ if (!entry)
+ return;
+
+ if (mLastFrame == aFrame) {
+ // Flush cache. We assume DeleteAllForEntry will be called before
+ // a frame is destroyed.
+ mLastFrame = nullptr;
+ mLastEntry = nullptr;
+ }
+
+ DeleteAllForEntry(entry);
+
+ // mLastEntry points into mEntries, so we use RawRemoveEntry() which will not
+ // resize mEntries.
+ mEntries.RawRemoveEntry(entry);
+}
+
+void
+FramePropertyTable::DeleteAll()
+{
+ mLastFrame = nullptr;
+ mLastEntry = nullptr;
+
+ for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
+ DeleteAllForEntry(iter.Get());
+ }
+ mEntries.Clear();
+}
+
+size_t
+FramePropertyTable::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return mEntries.SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace mozilla
diff --git a/layout/base/FramePropertyTable.h b/layout/base/FramePropertyTable.h
new file mode 100644
index 000000000..e9847efbf
--- /dev/null
+++ b/layout/base/FramePropertyTable.h
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FRAMEPROPERTYTABLE_H_
+#define FRAMEPROPERTYTABLE_H_
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.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 FramePropertyTable methods.
+ */
+template<typename T>
+struct FramePropertyDescriptor : public FramePropertyDescriptorUntyped
+{
+ typedef void Destructor(T* aPropertyValue);
+ typedef void DestructorWithFrame(const nsIFrame* aaFrame,
+ T* aPropertyValue);
+
+ template<Destructor Dtor>
+ static constexpr const FramePropertyDescriptor<T> NewWithDestructor()
+ {
+ return { Destruct<Dtor>, nullptr };
+ }
+
+ template<DestructorWithFrame Dtor>
+ static constexpr
+ const FramePropertyDescriptor<T> NewWithDestructorWithFrame()
+ {
+ return { nullptr, DestructWithFrame<Dtor> };
+ }
+
+ static constexpr const FramePropertyDescriptor<T> NewWithoutDestructor()
+ {
+ return { nullptr, nullptr };
+ }
+
+private:
+ constexpr FramePropertyDescriptor(
+ UntypedDestructor* aDtor, UntypedDestructorWithFrame* aDtorWithFrame)
+ : FramePropertyDescriptorUntyped(aDtor, aDtorWithFrame)
+ {}
+
+ template<Destructor Dtor>
+ static void Destruct(void* aPropertyValue)
+ {
+ Dtor(static_cast<T*>(aPropertyValue));
+ }
+
+ template<DestructorWithFrame Dtor>
+ static void DestructWithFrame(const nsIFrame* aFrame, void* aPropertyValue)
+ {
+ Dtor(aFrame, static_cast<T*>(aPropertyValue));
+ }
+};
+
+// SmallValueHolder<T> is a placeholder intended to be used as template
+// argument of FramePropertyDescriptor for types which can fit into the
+// size of a pointer directly. This class should never be defined, so
+// that we won't use it for unexpected purpose by mistake.
+template<typename T>
+class SmallValueHolder;
+
+namespace detail {
+
+template<typename T>
+struct FramePropertyTypeHelper
+{
+ typedef T* Type;
+};
+template<typename T>
+struct FramePropertyTypeHelper<SmallValueHolder<T>>
+{
+ typedef T Type;
+};
+
+}
+
+/**
+ * The FramePropertyTable 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.
+ *
+ * Property values are passed as void* but do not actually have to be
+ * valid pointers. You can use NS_INT32_TO_PTR/NS_PTR_TO_INT32 to
+ * store int32_t values. Null/zero values can be stored and retrieved.
+ * Of course, the destructor function (if any) must handle such values
+ * correctly.
+ */
+class FramePropertyTable {
+public:
+ template<typename T>
+ using Descriptor = const FramePropertyDescriptor<T>*;
+ using UntypedDescriptor = const FramePropertyDescriptorUntyped*;
+
+ template<typename T>
+ using PropertyType = typename detail::FramePropertyTypeHelper<T>::Type;
+
+ FramePropertyTable() : mLastFrame(nullptr), mLastEntry(nullptr)
+ {
+ }
+ ~FramePropertyTable()
+ {
+ DeleteAll();
+ }
+
+ /**
+ * Set a property value on a frame. This requires one hashtable
+ * lookup (using the frame as the key) and a linear search through
+ * the properties of that frame. Any existing value for the property
+ * is destroyed.
+ */
+ template<typename T>
+ void Set(const nsIFrame* aFrame, Descriptor<T> aProperty,
+ PropertyType<T> aValue)
+ {
+ void* ptr = ReinterpretHelper<T>::ToPointer(aValue);
+ SetInternal(aFrame, aProperty, ptr);
+ }
+
+ /**
+ * @return true if @aProperty is set for @aFrame. This requires one hashtable
+ * lookup (using the frame as the key) and a linear search through the
+ * properties of that 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 Remove() and check the aFoundResult outparam to find out whether the
+ * property is set. Legitimate non-assertion uses include:
+ *
+ * - Checking if a frame property is set in cases where that's all we want
+ * to know (i.e., we don't intend to read the actual value or remove the
+ * property).
+ *
+ * - Calling Has() before Set() in cases where we don't want to overwrite
+ * an existing value for the frame property.
+ */
+ template<typename T>
+ bool Has(const nsIFrame* aFrame, Descriptor<T> aProperty)
+ {
+ bool foundResult = false;
+ mozilla::Unused << GetInternal(aFrame, aProperty, &foundResult);
+ return foundResult;
+ }
+
+ /**
+ * Get a property value for a frame. This requires one hashtable
+ * lookup (using the frame as the key) and a linear search through
+ * the properties of that frame. If the frame has no such property,
+ * returns zero-filled result, which means null for pointers and
+ * zero for integers and floating point types.
+ * @param aFoundResult if non-null, receives a value 'true' iff
+ * the frame has a value for the property. This lets callers
+ * disambiguate a null result, which can mean 'no such property' or
+ * 'property value is null'.
+ */
+ template<typename T>
+ PropertyType<T> Get(const nsIFrame* aFrame, Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr)
+ {
+ void* ptr = GetInternal(aFrame, aProperty, aFoundResult);
+ return ReinterpretHelper<T>::FromPointer(ptr);
+ }
+ /**
+ * Remove a property value for a frame. This requires one hashtable
+ * lookup (using the frame as the key) and a linear search through
+ * the properties of that frame. The old property value is returned
+ * (and not destroyed). If the frame has no such property,
+ * returns zero-filled result, which means null for pointers and
+ * zero for integers and floating point types.
+ * @param aFoundResult if non-null, receives a value 'true' iff
+ * the frame had a value for the property. This lets callers
+ * disambiguate a null result, which can mean 'no such property' or
+ * 'property value is null'.
+ */
+ template<typename T>
+ PropertyType<T> Remove(const nsIFrame* aFrame, Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr)
+ {
+ void* ptr = RemoveInternal(aFrame, aProperty, aFoundResult);
+ return ReinterpretHelper<T>::FromPointer(ptr);
+ }
+ /**
+ * Remove and destroy a property value for a frame. This requires one
+ * hashtable lookup (using the frame as the key) and a linear search
+ * through the properties of that frame. If the frame has no such
+ * property, nothing happens.
+ */
+ template<typename T>
+ void Delete(const nsIFrame* aFrame, Descriptor<T> aProperty)
+ {
+ DeleteInternal(aFrame, aProperty);
+ }
+ /**
+ * Remove and destroy all property values for a frame. This requires one
+ * hashtable lookup (using the frame as the key).
+ */
+ void DeleteAllFor(const nsIFrame* aFrame);
+ /**
+ * Remove and destroy all property values for all frames.
+ */
+ void DeleteAll();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+protected:
+ void SetInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
+ void* aValue);
+
+ void* GetInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
+ bool* aFoundResult);
+
+ void* RemoveInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
+ bool* aFoundResult);
+
+ void DeleteInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty);
+
+ template<typename T>
+ struct ReinterpretHelper
+ {
+ static_assert(sizeof(PropertyType<T>) <= sizeof(void*),
+ "size of the value must never be larger than a pointer");
+
+ static void* ToPointer(PropertyType<T> aValue)
+ {
+ void* ptr = nullptr;
+ memcpy(&ptr, &aValue, sizeof(aValue));
+ return ptr;
+ }
+
+ static PropertyType<T> FromPointer(void* aPtr)
+ {
+ PropertyType<T> value;
+ memcpy(&value, &aPtr, sizeof(value));
+ return value;
+ }
+ };
+
+ template<typename T>
+ struct ReinterpretHelper<T*>
+ {
+ static void* ToPointer(T* aValue)
+ {
+ return static_cast<void*>(aValue);
+ }
+
+ static T* FromPointer(void* aPtr)
+ {
+ return static_cast<T*>(aPtr);
+ }
+ };
+
+ /**
+ * Stores a property descriptor/value pair. It can also be used to
+ * store an nsTArray of PropertyValues.
+ */
+ struct PropertyValue {
+ PropertyValue() : mProperty(nullptr), mValue(nullptr) {}
+ PropertyValue(UntypedDescriptor aProperty, void* aValue)
+ : mProperty(aProperty), mValue(aValue) {}
+
+ bool IsArray() { return !mProperty && mValue; }
+ nsTArray<PropertyValue>* ToArray()
+ {
+ NS_ASSERTION(IsArray(), "Must be array");
+ return reinterpret_cast<nsTArray<PropertyValue>*>(&mValue);
+ }
+
+ void DestroyValueFor(const nsIFrame* aFrame) {
+ if (mProperty->mDestructor) {
+ mProperty->mDestructor(mValue);
+ } else if (mProperty->mDestructorWithFrame) {
+ mProperty->mDestructorWithFrame(aFrame, mValue);
+ }
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ size_t n = 0;
+ // We don't need to measure mProperty because it always points to static
+ // memory. As for mValue: if it's a single value we can't measure it,
+ // because the type is opaque; if it's an array, we measure the array
+ // storage, but we can't measure the individual values, again because
+ // their types are opaque.
+ if (IsArray()) {
+ nsTArray<PropertyValue>* array = ToArray();
+ n += array->ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+ }
+
+ UntypedDescriptor mProperty;
+ void* 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;
+ }
+ };
+
+ /**
+ * Our hashtable entry. The key is an nsIFrame*, the value is a
+ * PropertyValue representing one or more property/value pairs.
+ */
+ class Entry : public nsPtrHashKey<const nsIFrame>
+ {
+ public:
+ explicit Entry(KeyTypePointer aKey) : nsPtrHashKey<const nsIFrame>(aKey) {}
+ Entry(const Entry &toCopy) :
+ nsPtrHashKey<const nsIFrame>(toCopy), mProp(toCopy.mProp) {}
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ return mProp.SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ PropertyValue mProp;
+ };
+
+ static void DeleteAllForEntry(Entry* aEntry);
+
+ // Note that mLastEntry points into mEntries, so we need to be careful about
+ // not triggering a resize of mEntries, e.g. use RawRemoveEntry() instead of
+ // RemoveEntry() in some places.
+ nsTHashtable<Entry> mEntries;
+ const nsIFrame* mLastFrame;
+ Entry* mLastEntry;
+};
+
+/**
+ * This class encapsulates the properties of a frame.
+ */
+class FrameProperties {
+public:
+ template<typename T> using Descriptor = FramePropertyTable::Descriptor<T>;
+ template<typename T> using PropertyType = FramePropertyTable::PropertyType<T>;
+
+ FrameProperties(FramePropertyTable* aTable, const nsIFrame* aFrame)
+ : mTable(aTable), mFrame(aFrame) {}
+
+ template<typename T>
+ void Set(Descriptor<T> aProperty, PropertyType<T> aValue) const
+ {
+ mTable->Set(mFrame, aProperty, aValue);
+ }
+
+ template<typename T>
+ bool Has(Descriptor<T> aProperty) const
+ {
+ return mTable->Has(mFrame, aProperty);
+ }
+
+ template<typename T>
+ PropertyType<T> Get(Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr) const
+ {
+ return mTable->Get(mFrame, aProperty, aFoundResult);
+ }
+ template<typename T>
+ PropertyType<T> Remove(Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr) const
+ {
+ return mTable->Remove(mFrame, aProperty, aFoundResult);
+ }
+ template<typename T>
+ void Delete(Descriptor<T> aProperty)
+ {
+ mTable->Delete(mFrame, aProperty);
+ }
+
+private:
+ FramePropertyTable* mTable;
+ const nsIFrame* mFrame;
+};
+
+} // namespace mozilla
+
+#endif /* FRAMEPROPERTYTABLE_H_ */
diff --git a/layout/base/GeometryUtils.cpp b/layout/base/GeometryUtils.cpp
new file mode 100644
index 000000000..8bbb8fbcd
--- /dev/null
+++ b/layout/base/GeometryUtils.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GeometryUtils.h"
+
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/GeometryUtilsBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMQuad.h"
+#include "mozilla/dom/DOMRect.h"
+#include "nsContentUtils.h"
+#include "nsIFrame.h"
+#include "nsGenericDOMDataNode.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsLayoutUtils.h"
+#include "nsSVGUtils.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)
+{
+ nsIDocument* doc = aNode->OwnerDoc();
+ doc->FlushPendingNotifications(Flush_Layout);
+ switch (aType) {
+ case GEOMETRY_NODE_ELEMENT:
+ return aNode->AsContent()->GetPrimaryFrame();
+ case GEOMETRY_NODE_TEXT: {
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ return presShell->FrameConstructor()->EnsureFrameForTextNode(
+ static_cast<nsGenericDOMDataNode*>(aNode));
+ }
+ return nullptr;
+ }
+ case GEOMETRY_NODE_DOCUMENT: {
+ nsIPresShell* presShell = doc->GetShell();
+ return presShell ? presShell->GetRootFrame() : nullptr;
+ }
+ default:
+ MOZ_ASSERT(false, "Unknown GeometryNodeType");
+ return nullptr;
+ }
+}
+
+static nsIFrame*
+GetFrameForGeometryNode(const Optional<OwningGeometryNode>& aGeometryNode,
+ nsINode* aDefaultNode)
+{
+ if (!aGeometryNode.WasPassed()) {
+ return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT);
+ }
+
+ const OwningGeometryNode& value = aGeometryNode.Value();
+ if (value.IsElement()) {
+ return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT);
+ }
+ if (value.IsDocument()) {
+ return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT);
+ }
+ return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT);
+}
+
+static nsIFrame*
+GetFrameForGeometryNode(const GeometryNode& aGeometryNode)
+{
+ if (aGeometryNode.IsElement()) {
+ return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT);
+ }
+ if (aGeometryNode.IsDocument()) {
+ return GetFrameForNode(&aGeometryNode.GetAsDocument(), GEOMETRY_NODE_DOCUMENT);
+ }
+ return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT);
+}
+
+static nsIFrame*
+GetFrameForNode(nsINode* aNode)
+{
+ if (aNode->IsElement()) {
+ return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT);
+ }
+ if (aNode == aNode->OwnerDoc()) {
+ return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT);
+ }
+ NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type");
+ return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT);
+}
+
+static nsIFrame*
+GetFirstNonAnonymousFrameForGeometryNode(const Optional<OwningGeometryNode>& aNode,
+ nsINode* aDefaultNode)
+{
+ nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode);
+ if (f) {
+ f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
+ }
+ return f;
+}
+
+static nsIFrame*
+GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode& aNode)
+{
+ nsIFrame* f = GetFrameForGeometryNode(aNode);
+ if (f) {
+ f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
+ }
+ return f;
+}
+
+static nsIFrame*
+GetFirstNonAnonymousFrameForNode(nsINode* aNode)
+{
+ nsIFrame* f = GetFrameForNode(aNode);
+ 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 = nsSVGUtils::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 = nsRect(nsPoint(0, 0), f->GetSize());
+ r.Inflate(f->GetUsedMargin());
+ break;
+ }
+ default: MOZ_ASSERT(false, "unknown box type"); return r;
+ }
+
+ return r;
+}
+
+class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
+public:
+ AccumulateQuadCallback(nsISupports* aParentObject,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ nsIFrame* aRelativeToFrame,
+ const nsPoint& aRelativeToBoxTopLeft,
+ CSSBoxType aBoxType)
+ : mParentObject(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;
+ }
+ }
+
+ virtual void AddBox(nsIFrame* aFrame) override
+ {
+ nsIFrame* f = aFrame;
+ if (mBoxType == CSSBoxType::Margin &&
+ f->GetType() == nsGkAtoms::tableFrame) {
+ // 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(f, mRelativeToFrame, 4, points);
+ if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ CSSPoint delta(nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
+ for (uint32_t i = 0; i < 4; ++i) {
+ points[i] -= delta;
+ }
+ } else {
+ PodArrayZero(points);
+ }
+ mResult.AppendElement(new DOMQuad(mParentObject, points));
+ }
+
+ nsISupports* mParentObject;
+ nsTArray<RefPtr<DOMQuad> >& mResult;
+ nsIFrame* mRelativeToFrame;
+ nsPoint mRelativeToBoxTopLeft;
+ CSSBoxType mBoxType;
+};
+
+static nsPresContext*
+FindTopLevelPresContext(nsPresContext* aPC)
+{
+ bool isChrome = aPC->IsChrome();
+ nsPresContext* pc = aPC;
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent || parent->IsChrome() != isChrome) {
+ return pc;
+ }
+ pc = parent;
+ }
+}
+
+static bool
+CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1, nsIFrame* aFrame2)
+{
+ nsPresContext* pc1 = aFrame1->PresContext();
+ nsPresContext* pc2 = aFrame2->PresContext();
+ if (pc1 == pc2) {
+ return true;
+ }
+ if (nsContentUtils::IsCallerChrome()) {
+ return true;
+ }
+ if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
+ return true;
+ }
+ return false;
+}
+
+void GetBoxQuads(nsINode* aNode,
+ const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ ErrorResult& aRv)
+{
+ nsIFrame* frame = GetFrameForNode(aNode);
+ if (!frame) {
+ // No boxes to return
+ return;
+ }
+ nsWeakFrame weakFrame(frame);
+ nsIDocument* ownerDoc = aNode->OwnerDoc();
+ nsIFrame* relativeToFrame =
+ GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc);
+ // 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);
+ if (!frame) {
+ // No boxes to return
+ return;
+ }
+ }
+ if (!relativeToFrame) {
+ aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+ if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+ // GetBoxRectForFrame can modify relativeToFrame so call it first.
+ nsPoint relativeToTopLeft =
+ GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft();
+ AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame,
+ relativeToTopLeft, aOptions.mBox);
+ nsLayoutUtils::GetAllInFlowBoxes(frame, &callback);
+}
+
+static void
+TransformPoints(nsINode* aTo, const GeometryNode& aFrom,
+ uint32_t aPointCount, CSSPoint* aPoints,
+ const ConvertCoordinateOptions& aOptions, ErrorResult& aRv)
+{
+ nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
+ nsWeakFrame 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.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+ if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ 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(fromFrame, toFrame, aPointCount, aPoints);
+ if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y));
+ for (uint32_t i = 0; i < aPointCount; ++i) {
+ aPoints[i] -= toOffsetGfx;
+ }
+ } else {
+ PodZero(aPoints, aPointCount);
+ }
+}
+
+already_AddRefed<DOMQuad>
+ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ 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.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ points[i] = CSSPoint(p->X(), p->Y());
+ }
+ TransformPoints(aTo, aFrom, 4, points, aOptions, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
+ return result.forget();
+}
+
+already_AddRefed<DOMQuad>
+ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ 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, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
+ return result.forget();
+}
+
+already_AddRefed<DOMPoint>
+ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv)
+{
+ if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ CSSPoint point(aPoint.mX, aPoint.mY);
+ TransformPoints(aTo, aFrom, 1, &point, aOptions, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMPoint> result = new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
+ return result.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/base/GeometryUtils.h b/layout/base/GeometryUtils.h
new file mode 100644
index 000000000..d01e63169
--- /dev/null
+++ b/layout/base/GeometryUtils.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GEOMETRYUTILS_H_
+#define MOZILLA_GEOMETRYUTILS_H_
+
+#include "mozilla/ErrorResult.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+/**
+ * This file defines utility functions for converting between layout
+ * coordinate systems.
+ */
+
+class nsINode;
+
+namespace mozilla {
+
+namespace dom {
+struct BoxQuadOptions;
+struct ConvertCoordinateOptions;
+class DOMQuad;
+class DOMRectReadOnly;
+class DOMPoint;
+struct DOMPointInit;
+class OwningTextOrElementOrDocument;
+class TextOrElementOrDocument;
+} // namespace dom
+
+typedef dom::TextOrElementOrDocument GeometryNode;
+typedef dom::OwningTextOrElementOrDocument OwningGeometryNode;
+
+/**
+ * Computes quads for aNode using aOptions, according to GeometryUtils.getBoxQuads.
+ * May set an error in aRv.
+ */
+void GetBoxQuads(nsINode* aNode,
+ const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<dom::DOMQuad> >& aResult,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMQuad>
+ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMQuad>
+ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMPoint>
+ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint,
+ const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GEOMETRYUTILS_H_ */
diff --git a/layout/base/LayerState.h b/layout/base/LayerState.h
new file mode 100644
index 000000000..643e1e974
--- /dev/null
+++ b/layout/base/LayerState.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LAYERSTATE_H_
+#define LAYERSTATE_H_
+
+namespace mozilla {
+
+enum LayerState {
+ LAYER_NONE,
+ LAYER_INACTIVE,
+ LAYER_ACTIVE,
+ // Force an active layer even if it causes incorrect rendering, e.g.
+ // when the layer has rounded rect clips.
+ LAYER_ACTIVE_FORCE,
+ // Special layer that is metadata only.
+ LAYER_ACTIVE_EMPTY,
+ // Inactive style layer for rendering SVG effects.
+ LAYER_SVG_EFFECTS
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/LayoutLogging.cpp b/layout/base/LayoutLogging.cpp
new file mode 100644
index 000000000..62f12c9bd
--- /dev/null
+++ b/layout/base/LayoutLogging.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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,
+ ("[%d] WARNING: %s: '%s', file %s, line %d",
+ base::GetCurrentProcId(),
+ aStr, aExpr, aFile, aLine));
+ } else {
+ MOZ_LOG(sLayoutLog,
+ mozilla::LogLevel::Warning,
+ ("[%d] 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 000000000..f343d1aba
--- /dev/null
+++ b/layout/base/LayoutLogging.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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/MaskLayerImageCache.cpp b/layout/base/MaskLayerImageCache.cpp
new file mode 100644
index 000000000..e9039070c
--- /dev/null
+++ b/layout/base/MaskLayerImageCache.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MaskLayerImageCache.h"
+#include "ImageContainer.h"
+
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+MaskLayerImageCache::MaskLayerImageCache()
+{
+ MOZ_COUNT_CTOR(MaskLayerImageCache);
+}
+MaskLayerImageCache::~MaskLayerImageCache()
+{
+ MOZ_COUNT_DTOR(MaskLayerImageCache);
+}
+
+void
+MaskLayerImageCache::Sweep()
+{
+ for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) {
+ const MaskLayerImageCache::MaskLayerImageKey* key = iter.Get()->mKey;
+ if (key->HasZeroLayerCount()) {
+ iter.Remove();
+ }
+ }
+}
+
+ImageContainer*
+MaskLayerImageCache::FindImageFor(const MaskLayerImageKey** aKey)
+{
+ if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) {
+ *aKey = entry->mKey.get();
+ return entry->mContainer;
+ }
+
+ return nullptr;
+}
+
+void
+MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey, ImageContainer* aContainer)
+{
+ MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey);
+ entry->mContainer = aContainer;
+}
+
+MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey()
+ : mRoundedClipRects()
+ , mLayerCount(0)
+{
+ MOZ_COUNT_CTOR(MaskLayerImageKey);
+}
+
+MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey(const MaskLayerImageKey& aKey)
+ : mRoundedClipRects(aKey.mRoundedClipRects)
+ , mLayerCount(aKey.mLayerCount)
+{
+ MOZ_COUNT_CTOR(MaskLayerImageKey);
+}
+
+MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey()
+{
+ MOZ_COUNT_DTOR(MaskLayerImageKey);
+}
+
+} // namespace mozilla
diff --git a/layout/base/MaskLayerImageCache.h b/layout/base/MaskLayerImageCache.h
new file mode 100644
index 000000000..b18fe5aa1
--- /dev/null
+++ b/layout/base/MaskLayerImageCache.h
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MASKLAYERIMAGECACHE_H_
+#define MASKLAYERIMAGECACHE_H_
+
+#include "DisplayItemClip.h"
+#include "nsAutoPtr.h"
+#include "nsPresContext.h"
+#include "mozilla/gfx/Matrix.h"
+
+namespace mozilla {
+
+namespace layers {
+class ImageContainer;
+class ShadowLayerForwarder;
+} // namespace layers
+
+/**
+ * Keeps a record of image containers for mask layers, containers are mapped
+ * from the rounded rects used to create them.
+ * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys.
+ * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey
+ * (heap-allocated so that a mask layer's userdata can keep a pointer to the
+ * key for its image, in spite of the hashtable moving its entries around).
+ * The key consists of the rounded rects used to create the mask,
+ * an nsRefPtr to the ImageContainer containing the image, and a count
+ * of the number of layers currently using this ImageContainer.
+ * When the key's layer count is zero, the cache
+ * may remove the entry, which deletes the key object.
+ */
+class MaskLayerImageCache
+{
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::ShadowLayerForwarder ShadowLayerForwarder;
+public:
+ MaskLayerImageCache();
+ ~MaskLayerImageCache();
+
+ /**
+ * Representation of a rounded rectangle in device pixel coordinates, in
+ * contrast to DisplayItemClip::RoundedRect, which uses app units.
+ * In particular, our internal representation uses a gfxRect, rather than
+ * an nsRect, so this class is easier to use with transforms.
+ */
+ struct PixelRoundedRect
+ {
+ PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect,
+ nsPresContext* aPresContext)
+ : mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width),
+ aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height))
+ {
+ MOZ_COUNT_CTOR(PixelRoundedRect);
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ mRadii[corner] = aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]);
+ }
+ }
+ PixelRoundedRect(const PixelRoundedRect& aPRR)
+ : mRect(aPRR.mRect)
+ {
+ MOZ_COUNT_CTOR(PixelRoundedRect);
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ mRadii[corner] = aPRR.mRadii[corner];
+ }
+ }
+
+ ~PixelRoundedRect()
+ {
+ MOZ_COUNT_DTOR(PixelRoundedRect);
+ }
+
+ // Applies the scale and translate components of aTransform.
+ // It is an error to pass a matrix which does more than just scale
+ // and translate.
+ void ScaleAndTranslate(const gfx::Matrix& aTransform)
+ {
+ NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0,
+ "Transform has a component other than scale and translate");
+
+ mRect = aTransform.TransformBounds(mRect);
+
+ for (size_t i = 0; i < ArrayLength(mRadii); i += 2) {
+ mRadii[i] *= aTransform._11;
+ mRadii[i + 1] *= aTransform._22;
+ }
+ }
+
+ bool operator==(const PixelRoundedRect& aOther) const {
+ if (!mRect.IsEqualInterior(aOther.mRect)) {
+ return false;
+ }
+
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ if (mRadii[corner] != aOther.mRadii[corner]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!=(const PixelRoundedRect& aOther) const {
+ return !(*this == aOther);
+ }
+
+ // Create a hash for this object.
+ PLDHashNumber Hash() const
+ {
+ PLDHashNumber hash = HashBytes(&mRect.x, 4*sizeof(gfxFloat));
+ hash = AddToHash(hash, HashBytes(mRadii, 8*sizeof(gfxFloat)));
+
+ return hash;
+ }
+
+ gfx::Rect mRect;
+ // Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
+ gfxFloat mRadii[8];
+
+ private:
+ PixelRoundedRect() = delete;
+ };
+
+ struct MaskLayerImageKeyRef;
+
+ /**
+ * A key to identify cached image containers.
+ * The const-ness of this class is with respect to its use as a key into a
+ * hashtable, so anything not used to create the hash is mutable.
+ * mLayerCount counts the number of mask layers which have a reference to
+ * MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData,
+ * which keeps a reference to the key. There will usually be mLayerCount + 1
+ * pointers to a key object (the +1 being from the hashtable entry), but this
+ * invariant may be temporarily broken.
+ */
+ struct MaskLayerImageKey
+ {
+ friend struct MaskLayerImageKeyRef;
+
+ MaskLayerImageKey();
+ MaskLayerImageKey(const MaskLayerImageKey& aKey);
+
+ ~MaskLayerImageKey();
+
+ bool HasZeroLayerCount() const {
+ return mLayerCount == 0;
+ }
+
+ PLDHashNumber Hash() const
+ {
+ PLDHashNumber hash = 0;
+
+ for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
+ hash = AddToHash(hash, mRoundedClipRects[i].Hash());
+ }
+ hash = AddToHash(hash, mForwarder.get());
+
+ return hash;
+ }
+
+ bool operator==(const MaskLayerImageKey& aOther) const
+ {
+ return mForwarder == aOther.mForwarder &&
+ mRoundedClipRects == aOther.mRoundedClipRects;
+ }
+
+ nsTArray<PixelRoundedRect> mRoundedClipRects;
+ RefPtr<ShadowLayerForwarder> mForwarder;
+ private:
+ void IncLayerCount() const { ++mLayerCount; }
+ void DecLayerCount() const
+ {
+ NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count");
+ --mLayerCount;
+ }
+ mutable uint32_t mLayerCount;
+ };
+
+ /**
+ * This struct maintains a reference to a MaskLayerImageKey, via a variant on
+ * refcounting. When a key is passed in via Reset(), we increment the
+ * passed-in key's mLayerCount, and we decrement its mLayerCount when we're
+ * destructed (or when the key is replaced via a second Reset() call).
+ *
+ * However, unlike standard refcounting smart-pointers, this object does
+ * *not* delete the tracked MaskLayerImageKey -- instead, deletion happens
+ * in MaskLayerImageCache::Sweep(), for any keys whose mLayerCount is 0.
+ */
+ struct MaskLayerImageKeyRef
+ {
+ ~MaskLayerImageKeyRef()
+ {
+ if (mRawPtr) {
+ mRawPtr->DecLayerCount();
+ }
+ }
+
+ MaskLayerImageKeyRef() : mRawPtr(nullptr) {}
+ MaskLayerImageKeyRef(const MaskLayerImageKeyRef&) = delete;
+ void operator=(const MaskLayerImageKeyRef&) = delete;
+
+ void Reset(const MaskLayerImageKey* aPtr)
+ {
+ MOZ_ASSERT(aPtr, "Cannot initialize a MaskLayerImageKeyRef with a null pointer");
+ aPtr->IncLayerCount();
+ if (mRawPtr) {
+ mRawPtr->DecLayerCount();
+ }
+ mRawPtr = aPtr;
+ }
+
+ private:
+ const MaskLayerImageKey* mRawPtr;
+ };
+
+ // Find an image container for aKey, returns nullptr if there is no suitable
+ // cached image. If there is an image, then aKey is set to point at the stored
+ // key for the image.
+ ImageContainer* FindImageFor(const MaskLayerImageKey** aKey);
+
+ // Add an image container with a key to the cache
+ // The image container used will be set as the container in aKey and aKey
+ // itself will be linked from this cache
+ void PutImage(const MaskLayerImageKey* aKey,
+ ImageContainer* aContainer);
+
+ // Sweep the cache for old image containers that can be deleted
+ void Sweep();
+
+protected:
+
+ class MaskLayerImageEntry : public PLDHashEntryHdr
+ {
+ public:
+ typedef const MaskLayerImageKey& KeyType;
+ typedef const MaskLayerImageKey* KeyTypePointer;
+
+ explicit MaskLayerImageEntry(KeyTypePointer aKey)
+ : mKey(aKey)
+ {
+ MOZ_COUNT_CTOR(MaskLayerImageEntry);
+ }
+ MaskLayerImageEntry(const MaskLayerImageEntry& aOther)
+ : mKey(aOther.mKey.get())
+ {
+ NS_ERROR("ALLOW_MEMMOVE == true, should never be called");
+ }
+ ~MaskLayerImageEntry()
+ {
+ MOZ_COUNT_DTOR(MaskLayerImageEntry);
+ }
+
+ // KeyEquals(): does this entry match this key?
+ bool KeyEquals(KeyTypePointer aKey) const
+ {
+ return *mKey == *aKey;
+ }
+
+ // KeyToPointer(): Convert KeyType to KeyTypePointer
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ // HashKey(): calculate the hash number
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ {
+ return aKey->Hash();
+ }
+
+ // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ // to use the copy constructor?
+ enum { ALLOW_MEMMOVE = true };
+
+ bool operator==(const MaskLayerImageEntry& aOther) const
+ {
+ return KeyEquals(aOther.mKey);
+ }
+
+ nsAutoPtr<const MaskLayerImageKey> mKey;
+ RefPtr<ImageContainer> mContainer;
+ };
+
+ nsTHashtable<MaskLayerImageEntry> mMaskImageContainers;
+};
+
+
+} // namespace mozilla
+
+
+#endif
diff --git a/layout/base/MobileViewportManager.cpp b/layout/base/MobileViewportManager.cpp
new file mode 100644
index 000000000..77289ef18
--- /dev/null
+++ b/layout/base/MobileViewportManager.cpp
@@ -0,0 +1,415 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MobileViewportManager.h"
+
+#include "gfxPrefs.h"
+#include "LayersLogging.h"
+#include "nsIDOMEvent.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresShell.h"
+#include "nsViewManager.h"
+#include "nsViewportInfo.h"
+#include "UnitTransforms.h"
+#include "nsIDocument.h"
+
+#define MVM_LOG(...)
+// #define MVM_LOG(...) printf_stderr("MVM: " __VA_ARGS__)
+
+NS_IMPL_ISUPPORTS(MobileViewportManager, nsIDOMEventListener, nsIObserver)
+
+static const nsLiteralString DOM_META_ADDED = NS_LITERAL_STRING("DOMMetaAdded");
+static const nsLiteralString DOM_META_CHANGED = NS_LITERAL_STRING("DOMMetaChanged");
+static const nsLiteralString FULL_ZOOM_CHANGE = NS_LITERAL_STRING("FullZoomChange");
+static const nsLiteralString LOAD = NS_LITERAL_STRING("load");
+static const nsLiteralCString BEFORE_FIRST_PAINT = NS_LITERAL_CSTRING("before-first-paint");
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+MobileViewportManager::MobileViewportManager(nsIPresShell* aPresShell,
+ nsIDocument* aDocument)
+ : mDocument(aDocument)
+ , mPresShell(aPresShell)
+ , mIsFirstPaint(false)
+ , mPainted(false)
+{
+ MOZ_ASSERT(mPresShell);
+ MOZ_ASSERT(mDocument);
+
+ MVM_LOG("%p: creating with presShell %p document %p\n", this, mPresShell, aDocument);
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ mEventTarget = window->GetChromeEventHandler();
+ }
+ if (mEventTarget) {
+ mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->AddEventListener(FULL_ZOOM_CHANGE, this, false);
+ mEventTarget->AddEventListener(LOAD, this, true);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
+ }
+}
+
+MobileViewportManager::~MobileViewportManager()
+{
+}
+
+void
+MobileViewportManager::Destroy()
+{
+ MVM_LOG("%p: destroying\n", this);
+
+ if (mEventTarget) {
+ mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->RemoveEventListener(FULL_ZOOM_CHANGE, this, false);
+ mEventTarget->RemoveEventListener(LOAD, this, true);
+ mEventTarget = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
+ }
+
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+void
+MobileViewportManager::SetRestoreResolution(float aResolution,
+ LayoutDeviceIntSize aDisplaySize)
+{
+ mRestoreResolution = Some(aResolution);
+ ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
+ PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ mRestoreDisplaySize = Some(restoreDisplaySize);
+}
+
+void
+MobileViewportManager::RequestReflow()
+{
+ MVM_LOG("%p: got a reflow request\n", this);
+ RefreshViewportSize(false);
+}
+
+void
+MobileViewportManager::ResolutionUpdated()
+{
+ MVM_LOG("%p: resolution updated\n", this);
+ RefreshSPCSPS();
+}
+
+NS_IMETHODIMP
+MobileViewportManager::HandleEvent(nsIDOMEvent* event)
+{
+ nsAutoString type;
+ event->GetType(type);
+
+ if (type.Equals(DOM_META_ADDED)) {
+ MVM_LOG("%p: got a dom-meta-added event\n", this);
+ RefreshViewportSize(mPainted);
+ } else if (type.Equals(DOM_META_CHANGED)) {
+ MVM_LOG("%p: got a dom-meta-changed event\n", this);
+ RefreshViewportSize(mPainted);
+ } else if (type.Equals(FULL_ZOOM_CHANGE)) {
+ MVM_LOG("%p: got a full-zoom-change event\n", this);
+ RefreshViewportSize(false);
+ } 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;
+}
+
+NS_IMETHODIMP
+MobileViewportManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (SameCOMIdentity(aSubject, mDocument) && 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)
+{
+ CSSToScreenScale zoom = aZoom;
+ 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);
+ }
+ return zoom;
+}
+
+LayoutDeviceToLayerScale
+MobileViewportManager::ScaleResolutionWithDisplayWidth(const LayoutDeviceToLayerScale& aRes,
+ const float& aDisplayWidthChangeRatio,
+ const CSSSize& aNewViewport,
+ const CSSSize& aOldViewport)
+{
+ float cssViewportChangeRatio = (aOldViewport.width == 0)
+ ? 1.0f : aNewViewport.width / aOldViewport.width;
+ LayoutDeviceToLayerScale newRes(aRes.scale * aDisplayWidthChangeRatio
+ / cssViewportChangeRatio);
+ MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, aRes.scale,
+ aDisplayWidthChangeRatio, cssViewportChangeRatio, newRes.scale);
+ return newRes;
+}
+
+CSSToScreenScale
+MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
+ const ScreenIntSize& aDisplaySize,
+ const CSSSize& aViewport,
+ const Maybe<float>& aDisplayWidthChangeRatio)
+{
+ CSSToLayoutDeviceScale cssToDev =
+ mPresShell->GetPresContext()->CSSToDevPixelScale();
+ LayoutDeviceToLayerScale res(mPresShell->GetResolution());
+
+ if (mIsFirstPaint) {
+ CSSToScreenScale defaultZoom;
+ if (mRestoreResolution) {
+ LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
+ if (mRestoreDisplaySize) {
+ CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
+ float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
+ ? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
+
+ restoreResolution =
+ ScaleResolutionWithDisplayWidth(restoreResolution,
+ restoreDisplayWidthChangeRatio,
+ aViewport,
+ prevViewport);
+ }
+ defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
+ MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
+ defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
+ } else {
+ defaultZoom = aViewportInfo.GetDefaultZoom();
+ MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
+ if (!aViewportInfo.IsDefaultZoomValid()) {
+ defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport);
+ MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale);
+ defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
+ }
+ }
+ MOZ_ASSERT(aViewportInfo.GetMinZoom() <= defaultZoom &&
+ defaultZoom <= aViewportInfo.GetMaxZoom());
+
+ CSSToParentLayerScale zoom = ViewTargetAs<ParentLayerPixel>(defaultZoom,
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+
+ LayoutDeviceToLayerScale resolution = zoom / cssToDev * ParentLayerToLayerScale(1);
+ MVM_LOG("%p: setting resolution %f\n", this, resolution.scale);
+ mPresShell->SetResolutionAndScaleTo(resolution.scale);
+
+ return defaultZoom;
+ }
+
+ // 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 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) {
+ res = ScaleResolutionWithDisplayWidth(res, aDisplayWidthChangeRatio.value(),
+ aViewport, mMobileViewportSize);
+ mPresShell->SetResolutionAndScaleTo(res.scale);
+ }
+
+ return ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+}
+
+void
+MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize,
+ const CSSToScreenScale& aZoom)
+{
+ ScreenSize compositionSize(aDisplaySize);
+ ScreenMargin scrollbars =
+ LayoutDeviceMargin::FromAppUnits(
+ nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
+ mPresShell->GetRootScrollFrame()),
+ mPresShell->GetPresContext()->AppUnitsPerDevPixel())
+ // Scrollbars are not subject to resolution scaling, so LD pixels =
+ // Screen pixels for them.
+ * LayoutDeviceToScreenScale(1.0f);
+
+ compositionSize.width -= scrollbars.LeftRight();
+ compositionSize.height -= scrollbars.TopBottom();
+ CSSSize compSize = compositionSize / aZoom;
+ MVM_LOG("%p: Setting SPCSPS %s\n", this, Stringify(compSize).c_str());
+ nsLayoutUtils::SetScrollPositionClampingScrollPortSize(mPresShell, compSize);
+}
+
+void
+MobileViewportManager::UpdateDisplayPortMargins()
+{
+ if (nsIFrame* root = mPresShell->GetRootScrollFrame()) {
+ bool hasDisplayPort = nsLayoutUtils::HasDisplayPort(root->GetContent());
+ bool hasResolution = mPresShell->ScaleToResolution() &&
+ 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;
+ }
+ nsIScrollableFrame* scrollable = do_QueryFrame(root);
+ nsLayoutUtils::CalculateAndSetDisplayPortMargins(scrollable,
+ nsLayoutUtils::RepaintMode::DoNotRepaint);
+ }
+}
+
+void
+MobileViewportManager::RefreshSPCSPS()
+{
+ // This function is a subset of RefreshViewportSize, and only updates the
+ // SPCSPS.
+
+ if (!gfxPrefs::APZAllowZooming()) {
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+
+ CSSToLayoutDeviceScale cssToDev =
+ mPresShell->GetPresContext()->CSSToDevPixelScale();
+ LayoutDeviceToLayerScale res(mPresShell->GetResolution());
+ CSSToScreenScale zoom = ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+
+ UpdateSPCSPS(displaySize, zoom);
+}
+
+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
+ // UpdateResolution 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 UpdateResolution to ensure we only do the resolution
+ // update in the right scenarios.
+
+ Maybe<float> displayWidthChangeRatio;
+ LayoutDeviceIntSize newDisplaySize;
+ if (nsLayoutUtils::GetContentViewerSize(mPresShell->GetPresContext(), newDisplaySize)) {
+ // See the comment in UpdateResolution for why we're doing this.
+ if (mDisplaySize.width > 0) {
+ if (aForceAdjustResolution || mDisplaySize.width != newDisplaySize.width) {
+ displayWidthChangeRatio = Some((float)newDisplaySize.width / (float)mDisplaySize.width);
+ }
+ } else if (aForceAdjustResolution) {
+ displayWidthChangeRatio = Some(1.0f);
+ }
+
+ MVM_LOG("%p: Display width change ratio is %f\n", this, displayWidthChangeRatio.valueOr(0.0f));
+ mDisplaySize = newDisplaySize;
+ }
+
+ MVM_LOG("%p: Computing CSS viewport using %d,%d\n", this,
+ mDisplaySize.width, mDisplaySize.height);
+ if (mDisplaySize.width == 0 || mDisplaySize.height == 0) {
+ // We can't do anything useful here, we should just bail out
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mDocument->GetViewportInfo(displaySize);
+
+ CSSSize viewport = viewportInfo.GetSize();
+ MVM_LOG("%p: Computed CSS viewport %s\n", this, Stringify(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 (gfxPrefs::APZAllowZooming()) {
+ CSSToScreenScale zoom = UpdateResolution(viewportInfo, displaySize, viewport,
+ displayWidthChangeRatio);
+ MVM_LOG("%p: New zoom is %f\n", this, zoom.scale);
+ UpdateSPCSPS(displaySize, zoom);
+ }
+ if (gfxPlatform::AsyncPanZoomEnabled()) {
+ UpdateDisplayPortMargins();
+ }
+
+ CSSSize oldSize = mMobileViewportSize;
+
+ // Update internal state.
+ mIsFirstPaint = false;
+ mMobileViewportSize = viewport;
+
+ // Kick off a reflow.
+ mPresShell->ResizeReflowIgnoreOverride(
+ nsPresContext::CSSPixelsToAppUnits(viewport.width),
+ nsPresContext::CSSPixelsToAppUnits(viewport.height),
+ nsPresContext::CSSPixelsToAppUnits(oldSize.width),
+ nsPresContext::CSSPixelsToAppUnits(oldSize.height));
+}
diff --git a/layout/base/MobileViewportManager.h b/layout/base/MobileViewportManager.h
new file mode 100644
index 000000000..63128b281
--- /dev/null
+++ b/layout/base/MobileViewportManager.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MobileViewportManager_h_
+#define MobileViewportManager_h_
+
+#include "mozilla/Maybe.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+#include "Units.h"
+
+class nsIDOMEventTarget;
+class nsIDocument;
+class nsIPresShell;
+
+class MobileViewportManager final : public nsIDOMEventListener
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIOBSERVER
+
+ MobileViewportManager(nsIPresShell* aPresShell,
+ nsIDocument* aDocument);
+ void Destroy();
+
+ /* 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);
+
+ /* Notify the MobileViewportManager that a reflow was requested in the
+ * presShell.*/
+ void RequestReflow();
+
+ /* Notify the MobileViewportManager that the resolution on the presShell was
+ * updated, and the SPCSPS needs to be updated. */
+ void ResolutionUpdated();
+
+private:
+ ~MobileViewportManager();
+
+ /* Called to compute the initial viewport on page load or before-first-paint,
+ * whichever happens first. */
+ void SetInitialViewport();
+
+ /* 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 SPCSPS. */
+ void RefreshSPCSPS();
+
+ /* Helper to clamp the given zoom by the min/max in the viewport info. */
+ mozilla::CSSToScreenScale ClampZoom(const mozilla::CSSToScreenScale& aZoom,
+ const nsViewportInfo& aViewportInfo);
+
+ /* Helper to update the given resolution according to changed display and viewport widths. */
+ mozilla::LayoutDeviceToLayerScale
+ ScaleResolutionWithDisplayWidth(const mozilla::LayoutDeviceToLayerScale& aRes,
+ const float& aDisplayWidthChangeRatio,
+ const mozilla::CSSSize& aNewViewport,
+ const mozilla::CSSSize& aOldViewport);
+
+ /* Updates the presShell resolution and returns the new zoom. */
+ mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo,
+ const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSSize& aViewport,
+ const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
+
+ /* Updates the scroll-position-clamping scrollport size */
+ void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSToScreenScale& aZoom);
+
+ /* Updates the displayport margins for the presShell's root scrollable frame */
+ void UpdateDisplayPortMargins();
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
+ nsCOMPtr<nsIDOMEventTarget> mEventTarget;
+ bool mIsFirstPaint;
+ bool mPainted;
+ mozilla::LayoutDeviceIntSize mDisplaySize;
+ mozilla::CSSSize mMobileViewportSize;
+ mozilla::Maybe<float> mRestoreResolution;
+ mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
+};
+
+#endif
+
diff --git a/layout/base/OverflowChangedTracker.h b/layout/base/OverflowChangedTracker.h
new file mode 100644
index 000000000..a18d64b46
--- /dev/null
+++ b/layout/base/OverflowChangedTracker.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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) {
+ 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->Properties().Get(
+ nsIFrame::DebugInitialOverflowPropertyApplied()),
+ "InitialOverflowProperty must be set first.");
+
+ nsOverflowAreas* overflow =
+ frame->Properties().Get(nsIFrame::InitialOverflowProperty());
+ if (overflow) {
+ // FinishAndStoreOverflow will change the overflow areas passed in,
+ // so make a copy.
+ nsOverflowAreas overflowCopy = *overflow;
+ frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize());
+ } else {
+ nsRect bounds(nsPoint(0, 0), frame->GetSize());
+ nsOverflowAreas 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();
+ while (parent &&
+ parent != mSubtreeRoot &&
+ parent->Combines3DTransformWithAncestors()) {
+ // Passing frames in between the frame and the establisher of
+ // 3D rendering context.
+ parent = parent->GetParent();
+ MOZ_ASSERT(parent, "Root frame should never return true for Combines3DTransformWithAncestors");
+ }
+ if (parent && parent != mSubtreeRoot) {
+ Entry* parentEntry = mEntryList.find(Entry(parent, entry->mDepth - 1));
+ if (parentEntry) {
+ parentEntry->mChangeKind = std::max(parentEntry->mChangeKind, CHILDREN_CHANGED);
+ } else {
+ mEntryList.insert(new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED));
+ }
+ }
+ }
+ delete entry;
+ }
+ }
+
+private:
+ struct Entry : SplayTreeNode<Entry>
+ {
+ Entry(nsIFrame* aFrame, uint32_t aDepth, ChangeKind aChangeKind = CHILDREN_CHANGED)
+ : mFrame(aFrame)
+ , mDepth(aDepth)
+ , mChangeKind(aChangeKind)
+ {}
+
+ bool operator==(const Entry& aOther) const
+ {
+ return mFrame == aOther.mFrame;
+ }
+
+ /**
+ * Sort by *reverse* depth in the tree, and break ties with
+ * the frame pointer.
+ */
+ bool operator<(const Entry& aOther) const
+ {
+ if (mDepth == aOther.mDepth) {
+ return mFrame < aOther.mFrame;
+ }
+ return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */
+ }
+
+ static int compare(const Entry& aOne, const Entry& aTwo)
+ {
+ if (aOne == aTwo) {
+ return 0;
+ } else if (aOne < aTwo) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ nsIFrame* mFrame;
+ /* Depth in the frame tree */
+ uint32_t mDepth;
+ ChangeKind mChangeKind;
+ };
+
+ /* A list of frames to process, sorted by their depth in the frame tree */
+ SplayTree<Entry, Entry> mEntryList;
+
+ /* Don't update overflow of this frame or its ancestors. */
+ const nsIFrame* mSubtreeRoot;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/PaintTracker.cpp b/layout/base/PaintTracker.cpp
new file mode 100644
index 000000000..d6c0ef523
--- /dev/null
+++ b/layout/base/PaintTracker.cpp
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/PaintTracker.h"
+
+namespace mozilla {
+
+int PaintTracker::gPaintTracker;
+
+} // namespace mozilla
diff --git a/layout/base/PaintTracker.h b/layout/base/PaintTracker.h
new file mode 100644
index 000000000..7f0406b0f
--- /dev/null
+++ b/layout/base/PaintTracker.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_PaintTracker_h
+#define mozilla_PaintTracker_h
+
+#include "mozilla/Attributes.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+class MOZ_STACK_CLASS PaintTracker
+{
+public:
+ PaintTracker() {
+ ++gPaintTracker;
+ }
+ ~PaintTracker() {
+ NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor");
+ --gPaintTracker;
+ }
+
+ static bool IsPainting() {
+ return !!gPaintTracker;
+ }
+
+private:
+ static int gPaintTracker;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PaintTracker_h
diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp
new file mode 100644
index 000000000..8374ab9d2
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -0,0 +1,656 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.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 "nsIFrame.h"
+#include <algorithm>
+#include "LayersLogging.h"
+
+// If debugging this code you may wish to enable this logging, and also
+// uncomment the DumpFrameTree call near the bottom of the file.
+#define PET_LOG(...)
+// #define PET_LOG(...) printf_stderr("PET: " __VA_ARGS__);
+
+namespace mozilla {
+
+/*
+ * The basic goal of FindFrameTargetedByInputEvent() is to find a good
+ * target element that can respond to mouse events. Both mouse events and touch
+ * events are targeted at this element. Note that even for touch events, we
+ * check responsiveness to mouse events. We assume Web authors
+ * designing for touch events will take their own steps to account for
+ * inaccurate touch events.
+ *
+ * GetClickableAncestor() encapsulates the heuristic that determines whether an
+ * element is expected to respond to mouse events. An element is deemed
+ * "clickable" if it has registered listeners for "click", "mousedown" or
+ * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
+ * <select>, <textarea>, <label>), or has role="button", or is a link, or
+ * is a suitable XUL element.
+ * Any descendant (in the same document) of a clickable element is also
+ * deemed clickable since events will propagate to the clickable element from its
+ * descendant.
+ *
+ * If the element directly under the event position is clickable (or
+ * event radii are disabled), we always use that element. Otherwise we collect
+ * all frames intersecting a rectangle around the event position (taking CSS
+ * transforms into account) and choose the best candidate in GetClosest().
+ * Only GetClickableAncestor() candidates are considered; if none are found,
+ * then we revert to targeting the element under the event position.
+ * We ignore candidates outside the document subtree rooted by the
+ * document of the element directly under the event position. This ensures that
+ * event listeners in ancestor documents don't make it completely impossible
+ * to target a non-clickable element in a child document.
+ *
+ * When both a frame and its ancestor are in the candidate list, we ignore
+ * the ancestor. Otherwise a large ancestor element with a mouse event listener
+ * and some descendant elements that need to be individually targetable would
+ * disable intelligent targeting of those descendants within its bounds.
+ *
+ * GetClosest() computes the transformed axis-aligned bounds of each
+ * candidate frame, then computes the Manhattan distance from the event point
+ * to the bounds rect (which can be zero). The frame with the
+ * shortest distance is chosen. For visited links we multiply the distance
+ * by a specified constant weight; this can be used to make visited links
+ * more or less likely to be targeted than non-visited links.
+ */
+
+struct EventRadiusPrefs
+{
+ uint32_t mVisitedWeight; // in percent, i.e. default is 100
+ uint32_t mSideRadii[4]; // TRBL order, in millimetres
+ bool mEnabled;
+ bool mRegistered;
+ bool mTouchOnly;
+ bool mRepositionEventCoords;
+ bool mTouchClusterDetectionEnabled;
+ bool mSimplifiedClusterDetection;
+ uint32_t mLimitReadableSize;
+ uint32_t mKeepLimitSizeForCluster;
+};
+
+static EventRadiusPrefs sMouseEventRadiusPrefs;
+static EventRadiusPrefs sTouchEventRadiusPrefs;
+
+static const EventRadiusPrefs*
+GetPrefsFor(EventClassID aEventClassID)
+{
+ EventRadiusPrefs* prefs = nullptr;
+ const char* prefBranch = nullptr;
+ if (aEventClassID == eTouchEventClass) {
+ prefBranch = "touch";
+ prefs = &sTouchEventRadiusPrefs;
+ } else if (aEventClassID == eMouseEventClass) {
+ // Mostly for testing purposes
+ prefBranch = "mouse";
+ prefs = &sMouseEventRadiusPrefs;
+ } else {
+ return nullptr;
+ }
+
+ if (!prefs->mRegistered) {
+ prefs->mRegistered = true;
+
+ nsPrintfCString enabledPref("ui.%s.radius.enabled", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mEnabled, enabledPref.get(), false);
+
+ nsPrintfCString visitedWeightPref("ui.%s.radius.visitedWeight", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mVisitedWeight, visitedWeightPref.get(), 100);
+
+ static const char prefNames[4][9] =
+ { "topmm", "rightmm", "bottommm", "leftmm" };
+ for (int32_t i = 0; i < 4; ++i) {
+ nsPrintfCString radiusPref("ui.%s.radius.%s", prefBranch, prefNames[i]);
+ Preferences::AddUintVarCache(&prefs->mSideRadii[i], radiusPref.get(), 0);
+ }
+
+ if (aEventClassID == eMouseEventClass) {
+ Preferences::AddBoolVarCache(&prefs->mTouchOnly,
+ "ui.mouse.radius.inputSource.touchOnly", true);
+ } else {
+ prefs->mTouchOnly = false;
+ }
+
+ nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
+
+ nsPrintfCString touchClusterPref("ui.zoomedview.enabled", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionEnabled, touchClusterPref.get(), false);
+
+ nsPrintfCString simplifiedClusterDetectionPref("ui.zoomedview.simplified", prefBranch);
+ Preferences::AddBoolVarCache(&prefs->mSimplifiedClusterDetection, simplifiedClusterDetectionPref.get(), false);
+
+ nsPrintfCString limitReadableSizePref("ui.zoomedview.limitReadableSize", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mLimitReadableSize, limitReadableSizePref.get(), 8);
+
+ nsPrintfCString keepLimitSize("ui.zoomedview.keepLimitSize", prefBranch);
+ Preferences::AddUintVarCache(&prefs->mKeepLimitSizeForCluster, keepLimitSize.get(), 16);
+ }
+
+ return prefs;
+}
+
+static bool
+HasMouseListener(nsIContent* aContent)
+{
+ if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
+ return elm->HasListenersFor(nsGkAtoms::onclick) ||
+ elm->HasListenersFor(nsGkAtoms::onmousedown) ||
+ elm->HasListenersFor(nsGkAtoms::onmouseup);
+ }
+
+ return false;
+}
+
+static bool gTouchEventsRegistered = false;
+static int32_t gTouchEventsEnabled = 0;
+
+static bool
+HasTouchListener(nsIContent* aContent)
+{
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ if (!gTouchEventsRegistered) {
+ Preferences::AddIntVarCache(&gTouchEventsEnabled,
+ "dom.w3c_touch_events.enabled", gTouchEventsEnabled);
+ gTouchEventsRegistered = true;
+ }
+
+ if (!gTouchEventsEnabled) {
+ return false;
+ }
+
+ return elm->HasListenersFor(nsGkAtoms::ontouchstart) ||
+ elm->HasListenersFor(nsGkAtoms::ontouchend);
+}
+
+static bool
+IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor, nsAutoString* aLabelTargetId)
+{
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
+ }
+ if (content == aAncestor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static nsIContent*
+GetClickableAncestor(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
+{
+ // Input events propagate up the content tree so we'll follow the content
+ // ancestors to look for elements accepting the click.
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (stopAt && content->IsHTMLElement(stopAt)) {
+ break;
+ }
+ if (HasTouchListener(content) || HasMouseListener(content)) {
+ return content;
+ }
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::button,
+ nsGkAtoms::input,
+ nsGkAtoms::select,
+ nsGkAtoms::textarea)) {
+ return content;
+ }
+ if (content->IsHTMLElement(nsGkAtoms::label)) {
+ if (aLabelTargetId) {
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
+ }
+ return content;
+ }
+
+ // Bug 921928: we don't have access to the content of remote iframe.
+ // So fluffing won't go there. We do an optimistic assumption here:
+ // that the content of the remote iframe needs to be a target.
+ if (content->IsHTMLElement(nsGkAtoms::iframe) &&
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser,
+ nsGkAtoms::_true, eIgnoreCase) &&
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ return content;
+ }
+
+ // See nsCSSFrameConstructor::FindXULTagData. This code is not
+ // really intended to be used with XUL, though.
+ if (content->IsAnyOfXULElements(nsGkAtoms::button,
+ nsGkAtoms::checkbox,
+ nsGkAtoms::radio,
+ nsGkAtoms::autorepeatbutton,
+ nsGkAtoms::menu,
+ nsGkAtoms::menubutton,
+ nsGkAtoms::menuitem,
+ nsGkAtoms::menulist,
+ nsGkAtoms::scrollbarbutton,
+ nsGkAtoms::resizer)) {
+ return content;
+ }
+
+ static nsIContent::AttrValuesArray clickableRoles[] =
+ { &nsGkAtoms::button, &nsGkAtoms::key, nullptr };
+ if (content->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
+ clickableRoles, eIgnoreCase) >= 0) {
+ return content;
+ }
+ if (content->IsEditable()) {
+ return content;
+ }
+ nsCOMPtr<nsIURI> linkURI;
+ if (content->IsLink(getter_AddRefs(linkURI))) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nscoord
+AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM)
+{
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float result = float(aMM) *
+ (pc->DeviceContext()->AppUnitsPerPhysicalInch() / MM_PER_INCH_FLOAT);
+ if (presShell->ScaleToResolution()) {
+ result = result / presShell->GetResolution();
+ }
+ return NSToCoordRound(result);
+}
+
+/**
+ * Clip aRect with the bounds of aFrame in the coordinate system of
+ * aRootFrame. aRootFrame is an ancestor of aFrame.
+ */
+static nsRect
+ClipToFrame(nsIFrame* aRootFrame, nsIFrame* aFrame, nsRect& aRect)
+{
+ nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
+ nsRect result = bound.Intersect(aRect);
+ return result;
+}
+
+static nsRect
+GetTargetRect(nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame,
+ nsIFrame* aRestrictToDescendants, const EventRadiusPrefs* aPrefs,
+ uint32_t aFlags)
+{
+ nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[0]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[1]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[2]),
+ AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[3]));
+ nsRect r(aPointRelativeToRootFrame, nsSize(0,0));
+ r.Inflate(m);
+ if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
+ // Don't clip this rect to the root scroll frame if the flag to ignore the
+ // root scroll frame is set. Note that the GetClosest code will still enforce
+ // that the target found is a descendant of aRestrictToDescendants.
+ r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
+ }
+ return r;
+}
+
+static float
+ComputeDistanceFromRect(const nsPoint& aPoint, const nsRect& aRect)
+{
+ nscoord dx = std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
+ nscoord dy = std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
+ return float(NS_hypot(dx, dy));
+}
+
+static float
+ComputeDistanceFromRegion(const nsPoint& aPoint, const nsRegion& aRegion)
+{
+ MOZ_ASSERT(!aRegion.IsEmpty(), "can't compute distance between point and empty region");
+ float minDist = -1;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ float dist = ComputeDistanceFromRect(aPoint, iter.Get());
+ if (dist < minDist || minDist < 0) {
+ minDist = dist;
+ }
+ }
+ return minDist;
+}
+
+// Subtract aRegion from aExposedRegion as long as that doesn't make the
+// exposed region get too complex or removes a big chunk of the exposed region.
+static void
+SubtractFromExposedRegion(nsRegion* aExposedRegion, const nsRegion& aRegion)
+{
+ if (aRegion.IsEmpty())
+ return;
+
+ nsRegion tmp;
+ tmp.Sub(*aExposedRegion, aRegion);
+ // Don't let *aExposedRegion get too complex, but don't let it fluff out to
+ // its bounds either. Do let aExposedRegion get more complex if by doing so
+ // we reduce its area by at least half.
+ if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area()/2) {
+ *aExposedRegion = tmp;
+ }
+}
+
+// Search in the list of frames aCandidates if the element with the id "aLabelTargetId"
+// is present.
+static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates, const nsAutoString& aLabelTargetId)
+{
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ nsIFrame* f = aCandidates[i];
+ nsIContent* aContent = f->GetContent();
+ if (aContent && aContent->IsElement()) {
+ if (aContent->GetID() && aLabelTargetId == nsAtomString(aContent->GetID())) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool
+IsLargeElement(nsIFrame* aFrame, const EventRadiusPrefs* aPrefs)
+{
+ uint32_t keepLimitSizeForCluster = aPrefs->mKeepLimitSizeForCluster;
+ nsSize frameSize = aFrame->GetSize();
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float cumulativeResolution = presShell->GetCumulativeResolution();
+ if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) > keepLimitSizeForCluster &&
+ (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) > keepLimitSizeForCluster) {
+ return true;
+ }
+ return false;
+}
+
+static nsIFrame*
+GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
+ const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
+ nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
+ nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster)
+{
+ std::vector<nsIContent*> mContentsInCluster; // List of content elements in the cluster without duplicate
+ nsIFrame* bestTarget = nullptr;
+ // Lower is better; distance is in appunits
+ float bestDistance = 1e6f;
+ nsRegion exposedRegion(aTargetRect);
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ nsIFrame* f = aCandidates[i];
+ PET_LOG("Checking candidate %p\n", f);
+
+ bool preservesAxisAlignedRectangles = false;
+ nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f,
+ nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles);
+ nsRegion region;
+ region.And(exposedRegion, borderBox);
+ if (region.IsEmpty()) {
+ PET_LOG(" candidate %p had empty hit region\n", f);
+ continue;
+ }
+
+ if (preservesAxisAlignedRectangles) {
+ // Subtract from the exposed region if we have a transform that won't make
+ // the bounds include a bunch of area that we don't actually cover.
+ SubtractFromExposedRegion(&exposedRegion, region);
+ }
+
+ nsAutoString labelTargetId;
+ if (aClickableAncestor && !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
+ PET_LOG(" candidate %p is not a descendant of required ancestor\n", f);
+ continue;
+ }
+
+ nsIContent* clickableContent = GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
+ if (!aClickableAncestor && !clickableContent) {
+ PET_LOG(" candidate %p was not clickable\n", f);
+ continue;
+ }
+ // If our current closest frame is a descendant of 'f', skip 'f' (prefer
+ // the nested frame).
+ if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) {
+ PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
+ continue;
+ }
+ if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) {
+ PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f, aRestrictToDescendants);
+ continue;
+ }
+
+ // If the first clickable ancestor of f is a label element
+ // and "for" attribute is present in label element, search the frame list for the "for" element
+ // If this element is present in the current list, do not count the frame in
+ // the cluster elements counter
+ if ((labelTargetId.IsEmpty() || !IsElementPresent(aCandidates, labelTargetId)) &&
+ !IsLargeElement(f, aPrefs)) {
+ if (std::find(mContentsInCluster.begin(), mContentsInCluster.end(), clickableContent) == mContentsInCluster.end()) {
+ mContentsInCluster.push_back(clickableContent);
+ }
+ }
+
+ // distance is in appunits
+ float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
+ nsIContent* content = f->GetContent();
+ if (content && content->IsElement() &&
+ content->AsElement()->State().HasState(
+ EventStates(NS_EVENT_STATE_VISITED))) {
+ distance *= aPrefs->mVisitedWeight / 100.0f;
+ }
+ if (distance < bestDistance) {
+ PET_LOG(" candidate %p is the new best\n", f);
+ bestDistance = distance;
+ bestTarget = f;
+ }
+ }
+ *aElementsInCluster = mContentsInCluster.size();
+ return bestTarget;
+}
+
+/*
+ * Return always true when touch cluster detection is OFF.
+ * When cluster detection is ON, return true:
+ * if the text inside the frame is readable (by human eyes)
+ * or
+ * if the structure is too complex to determine the size.
+ * In both cases, the frame is considered as clickable.
+ *
+ * Frames with a too small size will return false.
+ * In this case, the frame is considered not clickable.
+ */
+static bool
+IsElementClickableAndReadable(nsIFrame* aFrame, WidgetGUIEvent* aEvent, const EventRadiusPrefs* aPrefs)
+{
+ if (!aPrefs->mTouchClusterDetectionEnabled) {
+ return true;
+ }
+
+ if (aPrefs->mSimplifiedClusterDetection) {
+ return true;
+ }
+
+ if (aEvent->mClass != eMouseEventClass) {
+ return true;
+ }
+
+ uint32_t limitReadableSize = aPrefs->mLimitReadableSize;
+ nsSize frameSize = aFrame->GetSize();
+ nsPresContext* pc = aFrame->PresContext();
+ nsIPresShell* presShell = pc->PresShell();
+ float cumulativeResolution = presShell->GetCumulativeResolution();
+ if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) < limitReadableSize ||
+ (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) < limitReadableSize) {
+ return false;
+ }
+ // We want to detect small clickable text elements using the font size.
+ // Two common cases are supported for now:
+ // 1. text node
+ // 2. any element with only one child of type text node
+ // All the other cases are currently ignored.
+ nsIContent *content = aFrame->GetContent();
+ bool testFontSize = false;
+ if (content) {
+ nsINodeList* childNodes = content->ChildNodes();
+ uint32_t childNodeCount = childNodes->Length();
+ if ((content->IsNodeOfType(nsINode::eTEXT)) ||
+ // click occurs on the text inside <a></a> or other clickable tags with text inside
+
+ (childNodeCount == 1 && childNodes->Item(0) &&
+ childNodes->Item(0)->IsNodeOfType(nsINode::eTEXT))) {
+ // The click occurs on an element with only one text node child. In this case, the font size
+ // can be tested.
+ // The number of child nodes is tested to avoid the following cases (See bug 1172488):
+ // Some jscript libraries transform text elements into Canvas elements but keep the text nodes
+ // with a very small size (1px) to handle the selection of text.
+ // With such libraries, the font size of the text elements is not relevant to detect small elements.
+
+ testFontSize = true;
+ }
+ }
+
+ if (testFontSize) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ if (fm && fm->EmHeight() > 0 && // See bug 1171731
+ (pc->AppUnitsToGfxUnits(fm->EmHeight()) * cumulativeResolution) < limitReadableSize) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsIFrame*
+FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
+ nsIFrame* aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame,
+ uint32_t aFlags)
+{
+ uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ?
+ nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0;
+ nsIFrame* target =
+ nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags);
+ PET_LOG("Found initial target %p for event class %s point %s relative to root frame %p\n",
+ target, (aEvent->mClass == eMouseEventClass ? "mouse" :
+ (aEvent->mClass == eTouchEventClass ? "touch" : "other")),
+ mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(), aRootFrame);
+
+ const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass);
+ if (!prefs || !prefs->mEnabled) {
+ PET_LOG("Retargeting disabled\n");
+ return target;
+ }
+ nsIContent* clickableAncestor = nullptr;
+ if (target) {
+ clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
+ if (clickableAncestor) {
+ if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
+ aEvent->AsMouseEventBase()->hitCluster = true;
+ }
+ PET_LOG("Target %p is clickable\n", target);
+ // If the target that was directly hit has a clickable ancestor, that
+ // means it too is clickable. And since it is the same as or a descendant
+ // of clickableAncestor, it should become the root for the GetClosest
+ // search.
+ clickableAncestor = target->GetContent();
+ }
+ }
+
+ // Do not modify targeting for actual mouse hardware; only for mouse
+ // events generated by touch-screen hardware.
+ if (aEvent->mClass == eMouseEventClass &&
+ prefs->mTouchOnly &&
+ aEvent->AsMouseEvent()->inputSource !=
+ nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+ PET_LOG("Mouse input event is not from a touch source\n");
+ return target;
+ }
+
+ // If the exact target is non-null, only consider candidate targets in the same
+ // document as the exact target. Otherwise, if an ancestor document has
+ // a mouse event handler for example, targets that are !GetClickableAncestor can
+ // never be targeted --- something nsSubDocumentFrame in an ancestor document
+ // would be targeted instead.
+ nsIFrame* restrictToDescendants = target ?
+ target->PresContext()->PresShell()->GetRootFrame() : aRootFrame;
+
+ nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
+ restrictToDescendants, prefs, aFlags);
+ PET_LOG("Expanded point to target rect %s\n",
+ mozilla::layers::Stringify(targetRect).c_str());
+ AutoTArray<nsIFrame*,8> candidates;
+ nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags);
+ if (NS_FAILED(rv)) {
+ return target;
+ }
+
+ int32_t elementsInCluster = 0;
+
+ nsIFrame* closestClickable =
+ GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
+ restrictToDescendants, clickableAncestor, candidates,
+ &elementsInCluster);
+ if (closestClickable) {
+ if ((prefs->mTouchClusterDetectionEnabled && elementsInCluster > 1) ||
+ (!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {
+ if (aEvent->mClass == eMouseEventClass) {
+ WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();
+ mouseEventBase->hitCluster = true;
+ }
+ }
+ target = closestClickable;
+ }
+ PET_LOG("Final target is %p\n", target);
+
+ // Uncomment this to dump the frame tree to help with debugging.
+ // Note that dumping the frame tree at the top of the function may flood
+ // logcat on Android devices and cause the PET_LOGs to get dropped.
+ // aRootFrame->DumpFrameTree();
+
+ if (!target || !prefs->mRepositionEventCoords) {
+ // No repositioning required for this event
+ return target;
+ }
+
+ // Take the point relative to the root frame, make it relative to the target,
+ // clamp it to the bounds, and then make it relative to the root frame again.
+ nsPoint point = aPointRelativeToRootFrame;
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(aRootFrame, target, point)) {
+ return target;
+ }
+ point = target->GetRectRelativeToSelf().ClampPoint(point);
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(target, aRootFrame, point)) {
+ return target;
+ }
+ // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
+ // get back the (now-clamped) coordinates in the event's widget's space.
+ nsView* view = aRootFrame->GetView();
+ if (!view) {
+ return target;
+ }
+ LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
+ aRootFrame->PresContext(), view, point, aEvent->mWidget);
+ if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
+ // If that succeeded, we update the point in the event
+ aEvent->mRefPoint = widgetPoint;
+ }
+ return target;
+}
+
+} // namespace mozilla
diff --git a/layout/base/PositionedEventTargeting.h b/layout/base/PositionedEventTargeting.h
new file mode 100644
index 000000000..e6a2fddb9
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_PositionedEventTargeting_h
+#define mozilla_PositionedEventTargeting_h
+
+#include <stdint.h>
+#include "mozilla/EventForwards.h"
+
+class nsIFrame;
+struct nsPoint;
+
+namespace mozilla {
+
+enum {
+ INPUT_IGNORE_ROOT_SCROLL_FRAME = 0x01
+};
+/**
+ * Finds the target frame for a pointer event given the event type and location.
+ * This can look for frames within a rectangle surrounding the actual location
+ * that are suitable targets, to account for inaccurate pointing devices.
+ */
+nsIFrame*
+FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
+ nsIFrame* aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame,
+ uint32_t aFlags = 0);
+
+} // namespace mozilla
+
+#endif /* mozilla_PositionedEventTargeting_h */
diff --git a/layout/base/RestyleLogging.h b/layout/base/RestyleLogging.h
new file mode 100644
index 000000000..93c1c8ec6
--- /dev/null
+++ b/layout/base/RestyleLogging.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Macros used to log restyle events.
+ */
+
+#ifndef mozilla_RestyleLogging_h
+#define mozilla_RestyleLogging_h
+
+#include "mozilla/AutoRestore.h"
+
+#ifdef DEBUG
+#define RESTYLE_LOGGING
+#endif
+
+#ifdef RESTYLE_LOGGING
+#define LOG_RESTYLE_VAR2(prefix_, suffix_) prefix_##suffix_
+#define LOG_RESTYLE_VAR(prefix_, suffix_) LOG_RESTYLE_VAR2(prefix_, suffix_)
+#define LOG_RESTYLE_DEPTH LOG_RESTYLE_VAR(restyle_depth_, __LINE__)
+#define LOG_RESTYLE_IF(object_, cond_, message_, ...) \
+ PR_BEGIN_MACRO \
+ if (object_->ShouldLogRestyle() && (cond_)) { \
+ nsCString line; \
+ for (int32_t LOG_RESTYLE_VAR(rs_depth_, __LINE__) = 0; \
+ LOG_RESTYLE_VAR(rs_depth_, __LINE__) < object_->LoggingDepth(); \
+ LOG_RESTYLE_VAR(rs_depth_, __LINE__)++) { \
+ line.AppendLiteral(" "); \
+ } \
+ line.AppendPrintf(message_, ##__VA_ARGS__); \
+ printf_stderr("%s\n", line.get()); \
+ } \
+ PR_END_MACRO
+#define LOG_RESTYLE(message_, ...) \
+ LOG_RESTYLE_IF(this, true, message_, ##__VA_ARGS__)
+// Beware that LOG_RESTYLE_INDENT is two statements not wrapped in a block.
+#define LOG_RESTYLE_INDENT() \
+ AutoRestore<int32_t> LOG_RESTYLE_VAR(ar_depth_, __LINE__)(LoggingDepth()); \
+ ++LoggingDepth();
+#else
+#define LOG_RESTYLE_IF(cond_, message_, ...) /* nothing */
+#define LOG_RESTYLE(message_, ...) /* nothing */
+#define LOG_RESTYLE_INDENT() /* nothing */
+#endif
+
+#endif /* mozilla_RestyleLogging_h */
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
new file mode 100644
index 000000000..de8f10224
--- /dev/null
+++ b/layout/base/RestyleManager.cpp
@@ -0,0 +1,3974 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Code responsible for managing style changes: tracking what style
+ * changes need to happen, scheduling them, and doing them.
+ */
+
+#include "mozilla/RestyleManager.h"
+
+#include <algorithm> // For std::max
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventStates.h"
+#include "nsLayoutUtils.h"
+#include "AnimationCommon.h" // For GetLayerAnimationInfo
+#include "FrameLayerBuilder.h"
+#include "GeckoProfiler.h"
+#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
+#include "nsAutoPtr.h"
+#include "nsStyleChangeList.h"
+#include "nsRuleProcessorData.h"
+#include "nsStyleSet.h"
+#include "nsStyleUtil.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsSVGEffects.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsAnimationManager.h"
+#include "nsTransitionManager.h"
+#include "nsViewManager.h"
+#include "nsRenderingContext.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsContainerFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsBlockFrame.h"
+#include "nsViewportFrame.h"
+#include "SVGTextFrame.h"
+#include "StickyScrollContainer.h"
+#include "nsIRootBox.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsContentUtils.h"
+#include "nsIFrameInlines.h"
+#include "ActiveLayerTracker.h"
+#include "nsDisplayList.h"
+#include "RestyleTrackerInlines.h"
+#include "nsSMILAnimationController.h"
+#include "nsCSSRuleProcessor.h"
+#include "ChildIterator.h"
+#include "Layers.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+namespace mozilla {
+
+using namespace layers;
+using namespace dom;
+
+#define LOG_RESTYLE_CONTINUE(reason_, ...) \
+ LOG_RESTYLE("continuing restyle since " reason_, ##__VA_ARGS__)
+
+#ifdef RESTYLE_LOGGING
+static nsCString
+FrameTagToString(const nsIFrame* aFrame)
+{
+ nsCString result;
+ aFrame->ListTag(result);
+ return result;
+}
+
+static nsCString
+ElementTagToString(dom::Element* aElement)
+{
+ nsCString result;
+ nsDependentAtomString buf(aElement->NodeInfo()->NameAtom());
+ result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
+ return result;
+}
+#endif
+
+RestyleManager::RestyleManager(nsPresContext* aPresContext)
+ : RestyleManagerBase(aPresContext)
+ , mDoRebuildAllStyleData(false)
+ , mInRebuildAllStyleData(false)
+ , mSkipAnimationRules(false)
+ , mHavePendingNonAnimationRestyles(false)
+ , mRebuildAllExtraHint(nsChangeHint(0))
+ , mRebuildAllRestyleHint(nsRestyleHint(0))
+ , mAnimationGeneration(0)
+ , mReframingStyleContexts(nullptr)
+ , mAnimationsWithDestroyedFrame(nullptr)
+ , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
+ ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
+ ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)
+ , mIsProcessingRestyles(false)
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(0)
+#endif
+{
+ mPendingRestyles.Init(this);
+}
+
+void
+RestyleManager::RestyleElement(Element* aElement,
+ nsIFrame* aPrimaryFrame,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ NS_ASSERTION(aPrimaryFrame == aElement->GetPrimaryFrame(),
+ "frame/content mismatch");
+ if (aPrimaryFrame && aPrimaryFrame->GetContent() != aElement) {
+ // XXXbz this is due to image maps messing with the primary frame pointer
+ // of <area>s. See bug 135040. We can remove this block once that's fixed.
+ aPrimaryFrame = nullptr;
+ }
+ NS_ASSERTION(!aPrimaryFrame || aPrimaryFrame->GetContent() == aElement,
+ "frame/content mismatch");
+
+ // If we're restyling the root element and there are 'rem' units in
+ // use, handle dynamic changes to the definition of a 'rem' here.
+ if (PresContext()->UsesRootEMUnits() && aPrimaryFrame &&
+ !mInRebuildAllStyleData) {
+ nsStyleContext* oldContext = aPrimaryFrame->StyleContext();
+ if (!oldContext->GetParent()) { // check that we're the root element
+ RefPtr<nsStyleContext> newContext = StyleSet()->
+ ResolveStyleFor(aElement, nullptr /* == oldContext->GetParent() */);
+ if (oldContext->StyleFont()->mFont.size !=
+ newContext->StyleFont()->mFont.size) {
+ // The basis for 'rem' units has changed.
+ mRebuildAllRestyleHint |= aRestyleHint;
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ mRebuildAllRestyleHint |= eRestyle_Subtree;
+ }
+ mRebuildAllExtraHint |= aMinHint;
+ StartRebuildAllStyleData(aRestyleTracker);
+ return;
+ }
+ }
+ }
+
+ if (aMinHint & nsChangeHint_ReconstructFrame) {
+ FrameConstructor()->RecreateFramesForContent(aElement, false,
+ nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr);
+ } else if (aPrimaryFrame) {
+ ComputeAndProcessStyleChange(aPrimaryFrame, aMinHint, aRestyleTracker,
+ aRestyleHint, aRestyleHintData);
+ } else if (aRestyleHint & ~eRestyle_LaterSiblings) {
+ // We're restyling an element with no frame, so we should try to
+ // make one if its new style says it should have one. But in order
+ // to try to honor the restyle hint (which we'd like to do so that,
+ // for example, an animation-only style flush doesn't flush other
+ // buffered style changes), we only do this if the restyle hint says
+ // we have *some* restyling for this frame. This means we'll
+ // potentially get ahead of ourselves in that case, but not as much
+ // as we would if we didn't check the restyle hint.
+ nsStyleContext* newContext =
+ FrameConstructor()->MaybeRecreateFramesForElement(aElement);
+ if (newContext &&
+ newContext->StyleDisplay()->mDisplay == StyleDisplay::Contents) {
+ // Style change for a display:contents node that did not recreate frames.
+ ComputeAndProcessStyleChange(newContext, aElement, aMinHint,
+ aRestyleTracker, aRestyleHint,
+ aRestyleHintData);
+ }
+ }
+}
+
+RestyleManager::ReframingStyleContexts::ReframingStyleContexts(
+ RestyleManager* aRestyleManager)
+ : mRestyleManager(aRestyleManager)
+ , mRestorePointer(mRestyleManager->mReframingStyleContexts)
+{
+ MOZ_ASSERT(!mRestyleManager->mReframingStyleContexts,
+ "shouldn't construct recursively");
+ mRestyleManager->mReframingStyleContexts = this;
+}
+
+RestyleManager::ReframingStyleContexts::~ReframingStyleContexts()
+{
+ // Before we go away, we need to flush out any frame construction that
+ // was enqueued, so that we initiate transitions.
+ // Note that this is a little bit evil in that we're calling into code
+ // that calls our member functions from our destructor, but it's at
+ // the beginning of our destructor, so it shouldn't be too bad.
+ mRestyleManager->PresContext()->FrameConstructor()->CreateNeededFrames();
+}
+
+RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
+ RestyleManager* aRestyleManager)
+ : mRestyleManager(aRestyleManager)
+ , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame)
+{
+ MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
+ "shouldn't construct recursively");
+ mRestyleManager->mAnimationsWithDestroyedFrame = this;
+}
+
+void
+RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsForElementsWithoutFrames()
+{
+ StopAnimationsWithoutFrame(mContents, CSSPseudoElementType::NotPseudo);
+ StopAnimationsWithoutFrame(mBeforeContents, CSSPseudoElementType::before);
+ StopAnimationsWithoutFrame(mAfterContents, CSSPseudoElementType::after);
+}
+
+void
+RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsWithoutFrame(
+ nsTArray<RefPtr<nsIContent>>& aArray,
+ CSSPseudoElementType aPseudoType)
+{
+ nsAnimationManager* animationManager =
+ mRestyleManager->PresContext()->AnimationManager();
+ nsTransitionManager* transitionManager =
+ mRestyleManager->PresContext()->TransitionManager();
+ for (nsIContent* content : aArray) {
+ if (content->GetPrimaryFrame()) {
+ continue;
+ }
+ dom::Element* element = content->AsElement();
+
+ animationManager->StopAnimationsForElement(element, aPseudoType);
+ transitionManager->StopTransitionsForElement(element, aPseudoType);
+
+ // All other animations should keep running but not running on the
+ // *compositor* at this point.
+ EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType);
+ if (effectSet) {
+ for (KeyframeEffectReadOnly* effect : *effectSet) {
+ effect->ResetIsRunningOnCompositor();
+ }
+ }
+ }
+}
+
+static inline dom::Element*
+ElementForStyleContext(nsIContent* aParentContent,
+ nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType);
+
+// Forwarded nsIDocumentObserver method, to handle restyling (and
+// passing the notification to the frame).
+nsresult
+RestyleManager::ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask)
+{
+ // XXXbz it would be good if this function only took Elements, but
+ // we'd have to make ESM guarantee that usefully.
+ if (!aContent->IsElement()) {
+ return NS_OK;
+ }
+
+ Element* aElement = aContent->AsElement();
+
+ nsChangeHint changeHint;
+ nsRestyleHint restyleHint;
+ ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+ PostRestyleEvent(aElement, restyleHint, changeHint);
+ return NS_OK;
+}
+
+// Forwarded nsIMutationObserver method, to handle restyling.
+void
+RestyleManager::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ RestyleHintData rsdata;
+ nsRestyleHint rshint =
+ StyleSet()->HasAttributeDependentStyle(aElement,
+ aNameSpaceID,
+ aAttribute,
+ aModType,
+ false,
+ aNewValue,
+ rsdata);
+ PostRestyleEvent(aElement, rshint, nsChangeHint(0), &rsdata);
+}
+
+// Forwarded nsIMutationObserver method, to handle restyling (and
+// passing the notification to the frame).
+void
+RestyleManager::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ // Hold onto the PresShell to prevent ourselves from being destroyed.
+ // XXXbz how, exactly, would this attribute change cause us to be
+ // destroyed from inside this function?
+ nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
+ mozilla::Unused << shell; // Unused within this function
+
+ // Get the frame associated with the content which is the highest in the frame tree
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
+#if 0
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("RestyleManager::AttributeChanged: content=%p[%s] frame=%p",
+ aContent, ContentTag(aElement, 0), frame));
+#endif
+
+ // the style tag has its own interpretation based on aHint
+ nsChangeHint hint = aElement->GetAttributeChangeHint(aAttribute, aModType);
+
+ bool reframe = (hint & nsChangeHint_ReconstructFrame) != 0;
+
+#ifdef MOZ_XUL
+ // The following listbox widget trap prevents offscreen listbox widget
+ // content from being removed and re-inserted (which is what would
+ // happen otherwise).
+ if (!primaryFrame && !reframe) {
+ int32_t namespaceID;
+ nsIAtom* tag = PresContext()->Document()->BindingManager()->
+ ResolveTag(aElement, &namespaceID);
+
+ if (namespaceID == kNameSpaceID_XUL &&
+ (tag == nsGkAtoms::listitem ||
+ tag == nsGkAtoms::listcell))
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::tooltiptext ||
+ aAttribute == nsGkAtoms::tooltip)
+ {
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox) {
+ if (aModType == nsIDOMMutationEvent::REMOVAL)
+ rootBox->RemoveTooltipSupport(aElement);
+ if (aModType == nsIDOMMutationEvent::ADDITION)
+ rootBox->AddTooltipSupport(aElement);
+ }
+ }
+
+#endif // MOZ_XUL
+
+ if (primaryFrame) {
+ // See if we have appearance information for a theme.
+ const nsStyleDisplay* disp = primaryFrame->StyleDisplay();
+ if (disp->mAppearance) {
+ nsITheme* theme = PresContext()->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(PresContext(), primaryFrame, disp->mAppearance)) {
+ bool repaint = false;
+ theme->WidgetStateChanged(primaryFrame, disp->mAppearance, aAttribute,
+ &repaint, aOldValue);
+ if (repaint)
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ // let the frame deal with it now, so we don't have to deal later
+ primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ // XXXwaterson should probably check for IB split siblings
+ // here, and propagate the AttributeChanged notification to
+ // them, as well. Currently, inline frames don't do anything on
+ // this notification, so it's not that big a deal.
+ }
+
+ // See if we can optimize away the style re-resolution -- must be called after
+ // the frame's AttributeChanged() in case it does something that affects the style
+ RestyleHintData rsdata;
+ nsRestyleHint rshint =
+ StyleSet()->HasAttributeDependentStyle(aElement,
+ aNameSpaceID,
+ aAttribute,
+ aModType,
+ true,
+ aOldValue,
+ rsdata);
+ PostRestyleEvent(aElement, rshint, hint, &rsdata);
+}
+
+/* static */ uint64_t
+RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aFrame)
+{
+ EffectSet* effectSet = EffectSet::GetEffectSet(aFrame);
+ return effectSet ? effectSet->GetAnimationGeneration() : 0;
+}
+
+void
+RestyleManager::RestyleForEmptyChange(Element* aContainer)
+{
+ // In some cases (:empty + E, :empty ~ E), a change in the content of
+ // an element requires restyling its parent's siblings.
+ nsRestyleHint hint = eRestyle_Subtree;
+ nsIContent* grandparent = aContainer->GetParent();
+ if (grandparent &&
+ (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
+ hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
+ }
+ PostRestyleEvent(aContainer, hint, nsChangeHint(0));
+}
+
+void
+RestyleManager::RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ // The container cannot be a document, but might be a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+#ifdef DEBUG
+ {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+ }
+ }
+#endif
+ uint32_t selectorFlags =
+ container->GetFlags() & (NODE_ALL_SELECTOR_FLAGS &
+ ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool wasEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* cur = container->GetFirstChild();
+ cur != aFirstNewContent;
+ cur = cur->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(cur, true, false)) {
+ wasEmpty = false;
+ break;
+ }
+ }
+ if (wasEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the last element child before this node
+ for (nsIContent* cur = aFirstNewContent->GetPreviousSibling();
+ cur;
+ cur = cur->GetPreviousSibling()) {
+ if (cur->IsElement()) {
+ PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+ break;
+ }
+ }
+ }
+}
+
+// Needed since we can't use PostRestyleEvent on non-elements (with
+// eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
+// eRestyle_LaterSiblings) as appropriate).
+static void
+RestyleSiblingsStartingWith(RestyleManager* aRestyleManager,
+ nsIContent* aStartingSibling /* may be null */)
+{
+ for (nsIContent* sibling = aStartingSibling; sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (sibling->IsElement()) {
+ aRestyleManager->
+ PostRestyleEvent(sibling->AsElement(),
+ nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
+ nsChangeHint(0));
+ break;
+ }
+ }
+}
+
+// Restyling for a ContentInserted or CharacterDataChanged notification.
+// This could be used for ContentRemoved as well if we got the
+// notification before the removal happened (and sometimes
+// CharacterDataChanged is more like a removal than an addition).
+// The comments are written and variables are named in terms of it being
+// a ContentInserted notification.
+void
+RestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild)
+{
+ // The container might be a document or a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+ NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+ uint32_t selectorFlags =
+ container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool wasEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* child = container->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child == aChild)
+ continue;
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+ wasEmpty = false;
+ break;
+ }
+ }
+ if (wasEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the previously-first element child if it is after this node
+ bool passedChild = false;
+ for (nsIContent* content = container->GetFirstChild();
+ content;
+ content = content->GetNextSibling()) {
+ if (content == aChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ // restyle the previously-last element child if it is before this node
+ passedChild = false;
+ for (nsIContent* content = container->GetLastChild();
+ content;
+ content = content->GetPreviousSibling()) {
+ if (content == aChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ }
+}
+
+void
+RestyleManager::ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling)
+{
+ // The container might be a document or a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+ if (aOldChild->IsRootOfAnonymousSubtree()) {
+ // This should be an assert, but this is called incorrectly in
+ // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
+ // up the logs. Make it an assert again when that's fixed.
+ MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+ "anonymous nodes should not be in child lists (bug 439258)");
+ }
+ uint32_t selectorFlags =
+ container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool isEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* child = container->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+ isEmpty = false;
+ break;
+ }
+ }
+ if (isEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(this, aFollowingSibling);
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the now-first element child if it was after aOldChild
+ bool reachedFollowingSibling = false;
+ for (nsIContent* content = container->GetFirstChild();
+ content;
+ content = content->GetNextSibling()) {
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ // do NOT continue here; we might want to restyle this node
+ }
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ // restyle the now-last element child if it was before aOldChild
+ reachedFollowingSibling = (aFollowingSibling == nullptr);
+ for (nsIContent* content = container->GetLastChild();
+ content;
+ content = content->GetPreviousSibling()) {
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+ }
+ break;
+ }
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ }
+ }
+ }
+}
+
+void
+RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
+ "Should not reconstruct the root of the frame tree. "
+ "Use ReconstructDocElementHierarchy instead.");
+ MOZ_ASSERT(!(aRestyleHint & ~(eRestyle_Subtree | eRestyle_ForceDescendants)),
+ "the only bits allowed in aRestyleHint are eRestyle_Subtree and "
+ "eRestyle_ForceDescendants");
+
+ mRebuildAllExtraHint |= aExtraHint;
+ mRebuildAllRestyleHint |= aRestyleHint;
+
+ // Processing the style changes could cause a flush that propagates to
+ // the parent frame and thus destroys the pres shell, so we must hold
+ // a reference.
+ nsCOMPtr<nsIPresShell> presShell = PresContext()->GetPresShell();
+ if (!presShell || !presShell->GetRootFrame()) {
+ mDoRebuildAllStyleData = false;
+ return;
+ }
+
+ // Make sure that the viewmanager will outlive the presshell
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ mozilla::Unused << vm; // Not used within this function
+
+ // We may reconstruct frames below and hence process anything that is in the
+ // tree. We don't want to get notified to process those items again after.
+ presShell->GetDocument()->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ mDoRebuildAllStyleData = true;
+
+ ProcessPendingRestyles();
+}
+
+void
+RestyleManager::StartRebuildAllStyleData(RestyleTracker& aRestyleTracker)
+{
+ MOZ_ASSERT(mIsProcessingRestyles);
+
+ nsIFrame* rootFrame = PresContext()->PresShell()->GetRootFrame();
+ if (!rootFrame) {
+ // No need to do anything.
+ return;
+ }
+
+ mInRebuildAllStyleData = true;
+
+ // Tell the style set to get the old rule tree out of the way
+ // so we can recalculate while maintaining rule tree immutability
+ nsresult rv = StyleSet()->BeginReconstruct();
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("unable to rebuild style data");
+ }
+
+ nsRestyleHint restyleHint = mRebuildAllRestyleHint;
+ nsChangeHint changeHint = mRebuildAllExtraHint;
+ mRebuildAllExtraHint = nsChangeHint(0);
+ mRebuildAllRestyleHint = nsRestyleHint(0);
+
+ restyleHint |= eRestyle_ForceDescendants;
+
+ if (!(restyleHint & eRestyle_Subtree) &&
+ (restyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants))) {
+ // We want this hint to apply to the root node's primary frame
+ // rather than the root frame, since it's the primary frame that has
+ // the styles for the root element (rather than the ancestors of the
+ // primary frame whose mContent is the root node but which have
+ // different styles). If we use up the hint for one of the
+ // ancestors that we hit first, then we'll fail to do the restyling
+ // we need to do.
+ Element* root = PresContext()->Document()->GetRootElement();
+ if (root) {
+ // If the root element is gone, dropping the hint on the floor
+ // should be fine.
+ aRestyleTracker.AddPendingRestyle(root, restyleHint, nsChangeHint(0));
+ }
+ restyleHint = nsRestyleHint(0);
+ }
+
+ // Recalculate all of the style contexts for the document, from the
+ // root frame. We can't do this with a change hint, since we can't
+ // post a change hint for the root frame.
+ // Note that we can ignore the return value of ComputeStyleChangeFor
+ // because we never need to reframe the root frame.
+ // XXX Does it matter that we're passing aExtraHint to the real root
+ // frame and not the root node's primary frame? (We could do
+ // roughly what we do for aRestyleHint above.)
+ ComputeAndProcessStyleChange(rootFrame,
+ changeHint, aRestyleTracker, restyleHint,
+ RestyleHintData());
+}
+
+void
+RestyleManager::FinishRebuildAllStyleData()
+{
+ MOZ_ASSERT(mInRebuildAllStyleData, "bad caller");
+
+ // Tell the style set it's safe to destroy the old rule tree. We
+ // must do this after the ProcessRestyledFrames call in case the
+ // change list has frame reconstructs in it (since frames to be
+ // reconstructed will still have their old style context pointers
+ // until they are destroyed).
+ StyleSet()->EndReconstruct();
+
+ mInRebuildAllStyleData = false;
+}
+
+void
+RestyleManager::ProcessPendingRestyles()
+{
+ NS_PRECONDITION(PresContext()->Document(), "No document? Pshaw!");
+ NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(),
+ "Missing a script blocker!");
+
+ // First do any queued-up frame creation. (We should really
+ // merge this into the rest of the process, though; see bug 827239.)
+ PresContext()->FrameConstructor()->CreateNeededFrames();
+
+ // Process non-animation restyles...
+ MOZ_ASSERT(!mIsProcessingRestyles,
+ "Nesting calls to ProcessPendingRestyles?");
+ mIsProcessingRestyles = true;
+
+ // Before we process any restyles, we need to ensure that style
+ // resulting from any animations is up-to-date, so that if any style
+ // changes we cause trigger transitions, we have the correct old style
+ // for starting the transition.
+ bool haveNonAnimation =
+ mHavePendingNonAnimationRestyles || mDoRebuildAllStyleData;
+ if (haveNonAnimation) {
+ ++mAnimationGeneration;
+ UpdateOnlyAnimationStyles();
+ } else {
+ // If we don't have non-animation style updates, then we have queued
+ // up animation style updates from the refresh driver tick. This
+ // doesn't necessarily include *all* animation style updates, since
+ // we might be suppressing main-thread updates for some animations,
+ // so we don't want to call UpdateOnlyAnimationStyles, which updates
+ // all animations. In other words, the work that we're about to do
+ // to process the pending restyles queue is a *subset* of the work
+ // that UpdateOnlyAnimationStyles would do, since we're *not*
+ // updating transitions that are running on the compositor thread
+ // and suppressed on the main thread.
+ //
+ // But when we update those styles, we want to suppress updates to
+ // transitions just like we do in UpdateOnlyAnimationStyles. So we
+ // want to tell the transition manager to act as though we're in
+ // UpdateOnlyAnimationStyles.
+ //
+ // FIXME: In the future, we might want to refactor the way the
+ // animation and transition manager do their refresh driver ticks so
+ // that we can use UpdateOnlyAnimationStyles, with a different
+ // boolean argument, for this update as well, instead of having them
+ // post style updates in their WillRefresh methods.
+ PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(true);
+ }
+
+ ProcessRestyles(mPendingRestyles);
+
+ if (!haveNonAnimation) {
+ PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(false);
+ }
+
+ mIsProcessingRestyles = false;
+
+ NS_ASSERTION(haveNonAnimation || !mHavePendingNonAnimationRestyles,
+ "should not have added restyles");
+ mHavePendingNonAnimationRestyles = false;
+
+ if (mDoRebuildAllStyleData) {
+ // We probably wasted a lot of work up above, but this seems safest
+ // and it should be rarely used.
+ // This might add us as a refresh observer again; that's ok.
+ ProcessPendingRestyles();
+
+ NS_ASSERTION(!mDoRebuildAllStyleData,
+ "repeatedly setting mDoRebuildAllStyleData?");
+ }
+
+ MOZ_ASSERT(!mInRebuildAllStyleData,
+ "should have called FinishRebuildAllStyleData");
+}
+
+void
+RestyleManager::BeginProcessingRestyles(RestyleTracker& aRestyleTracker)
+{
+ // Make sure to not rebuild quote or counter lists while we're
+ // processing restyles
+ PresContext()->FrameConstructor()->BeginUpdate();
+
+ mInStyleRefresh = true;
+
+ if (ShouldStartRebuildAllFor(aRestyleTracker)) {
+ mDoRebuildAllStyleData = false;
+ StartRebuildAllStyleData(aRestyleTracker);
+ }
+}
+
+void
+RestyleManager::EndProcessingRestyles()
+{
+ FlushOverflowChangedTracker();
+
+ MOZ_ASSERT(mAnimationsWithDestroyedFrame);
+ mAnimationsWithDestroyedFrame->
+ StopAnimationsForElementsWithoutFrames();
+
+ // Set mInStyleRefresh to false now, since the EndUpdate call might
+ // add more restyles.
+ mInStyleRefresh = false;
+
+ if (mInRebuildAllStyleData) {
+ FinishRebuildAllStyleData();
+ }
+
+ PresContext()->FrameConstructor()->EndUpdate();
+
+#ifdef DEBUG
+ PresContext()->PresShell()->VerifyStyleTree();
+#endif
+}
+
+void
+RestyleManager::UpdateOnlyAnimationStyles()
+{
+ bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+
+ nsIDocument* document = PresContext()->Document();
+ nsSMILAnimationController* animationController =
+ document->HasAnimationController() ?
+ document->GetAnimationController() :
+ nullptr;
+ bool doSMIL = animationController &&
+ animationController->MightHavePendingStyleUpdates();
+
+ if (!doCSS && !doSMIL) {
+ return;
+ }
+
+ nsTransitionManager* transitionManager = PresContext()->TransitionManager();
+
+ transitionManager->SetInAnimationOnlyStyleUpdate(true);
+
+ RestyleTracker tracker(ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE |
+ ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT);
+ tracker.Init(this);
+
+ if (doCSS) {
+ // FIXME: We should have the transition manager and animation manager
+ // add only the elements for which animations are currently throttled
+ // (i.e., animating on the compositor with main-thread style updates
+ // suppressed).
+ PresContext()->EffectCompositor()->AddStyleUpdatesTo(tracker);
+ }
+
+ if (doSMIL) {
+ animationController->AddStyleUpdatesTo(tracker);
+ }
+
+ ProcessRestyles(tracker);
+
+ transitionManager->SetInAnimationOnlyStyleUpdate(false);
+}
+
+void
+RestyleManager::PostRestyleEvent(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData)
+{
+ if (MOZ_UNLIKELY(IsDisconnected()) ||
+ MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
+ return;
+ }
+
+ if (aRestyleHint == 0 && !aMinChangeHint) {
+ // Nothing to do here
+ return;
+ }
+
+ mPendingRestyles.AddPendingRestyle(aElement, aRestyleHint, aMinChangeHint,
+ aRestyleHintData);
+
+ // Set mHavePendingNonAnimationRestyles for any restyle that could
+ // possibly contain non-animation styles (i.e., those that require us
+ // to do an animation-only style flush before processing style changes
+ // to ensure correct initialization of CSS transitions).
+ if (aRestyleHint & ~eRestyle_AllHintsWithAnimations) {
+ mHavePendingNonAnimationRestyles = true;
+ }
+
+ PostRestyleEventInternal(false);
+}
+
+void
+RestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
+ "Should not reconstruct the root of the frame tree. "
+ "Use ReconstructDocElementHierarchy instead.");
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_SomeDescendants),
+ "PostRebuildAllStyleDataEvent does not handle "
+ "eRestyle_SomeDescendants");
+
+ mDoRebuildAllStyleData = true;
+ mRebuildAllExtraHint |= aExtraHint;
+ mRebuildAllRestyleHint |= aRestyleHint;
+
+ // Get a restyle event posted if necessary
+ PostRestyleEventInternal(false);
+}
+
+// aContent must be the content for the frame in question, which may be
+// :before/:after content
+/* static */ bool
+RestyleManager::TryInitiatingTransition(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aOldStyleContext,
+ RefPtr<nsStyleContext>*
+ aNewStyleContext /* inout */)
+{
+ if (!aContent || !aContent->IsElement()) {
+ return false;
+ }
+
+ // Notify the transition manager. If it starts a transition,
+ // it might modify the new style context.
+ RefPtr<nsStyleContext> sc = *aNewStyleContext;
+ aPresContext->TransitionManager()->StyleContextChanged(
+ aContent->AsElement(), aOldStyleContext, aNewStyleContext);
+ return *aNewStyleContext != sc;
+}
+
+static dom::Element*
+ElementForStyleContext(nsIContent* aParentContent,
+ nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType)
+{
+ // We don't expect XUL tree stuff here.
+ NS_PRECONDITION(aPseudoType == CSSPseudoElementType::NotPseudo ||
+ aPseudoType == CSSPseudoElementType::AnonBox ||
+ aPseudoType < CSSPseudoElementType::Count,
+ "Unexpected pseudo");
+ // XXX see the comments about the various element confusion in
+ // ElementRestyler::Restyle.
+ if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+ return aFrame->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::AnonBox) {
+ return nullptr;
+ }
+
+ if (aPseudoType == CSSPseudoElementType::firstLetter) {
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::letterFrame,
+ "firstLetter pseudoTag without a nsFirstLetterFrame");
+ nsBlockFrame* block = nsBlockFrame::GetNearestAncestorBlock(aFrame);
+ return block->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::mozColorSwatch) {
+ MOZ_ASSERT(aFrame->GetParent() &&
+ aFrame->GetParent()->GetParent(),
+ "Color swatch frame should have a parent & grandparent");
+
+ nsIFrame* grandparentFrame = aFrame->GetParent()->GetParent();
+ MOZ_ASSERT(grandparentFrame->GetType() == nsGkAtoms::colorControlFrame,
+ "Color swatch's grandparent should be nsColorControlFrame");
+
+ return grandparentFrame->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::mozNumberText ||
+ aPseudoType == CSSPseudoElementType::mozNumberWrapper ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinBox ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinUp ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinDown) {
+ // Get content for nearest nsNumberControlFrame:
+ nsIFrame* f = aFrame->GetParent();
+ MOZ_ASSERT(f);
+ while (f->GetType() != nsGkAtoms::numberControlFrame) {
+ f = f->GetParent();
+ MOZ_ASSERT(f);
+ }
+ return f->GetContent()->AsElement();
+ }
+
+ if (aParentContent) {
+ return aParentContent->AsElement();
+ }
+
+ MOZ_ASSERT(aFrame->GetContent()->GetParent(),
+ "should not have got here for the root element");
+ return aFrame->GetContent()->GetParent()->AsElement();
+}
+
+/**
+ * Some pseudo-elements actually have a content node created for them,
+ * whereas others have only a frame but not a content node. In some
+ * cases, we want to support style attributes or states on those
+ * elements. For those pseudo-elements, we need to pass the
+ * anonymous pseudo-element content to selector matching processes in
+ * addition to the element that the pseudo-element is for; in other
+ * cases we should pass null instead. This function returns the
+ * pseudo-element content that we should pass.
+ */
+static dom::Element*
+PseudoElementForStyleContext(nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType)
+{
+ if (aPseudoType >= CSSPseudoElementType::Count) {
+ return nullptr;
+ }
+
+ if (nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aPseudoType) ||
+ nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudoType)) {
+ return aFrame->GetContent()->AsElement();
+ }
+
+ return nullptr;
+}
+
+/**
+ * FIXME: Temporary. Should merge with following function.
+ */
+static nsIFrame*
+GetPrevContinuationWithPossiblySameStyle(nsIFrame* aFrame)
+{
+ // Account for {ib} splits when looking for "prevContinuation". In
+ // particular, for the first-continuation of a part of an {ib} split
+ // we want to use the previous ib-split sibling of the previous
+ // ib-split sibling of aFrame, which should have the same style
+ // context as aFrame itself. In particular, if aFrame is the first
+ // continuation of an inline part of a block-in-inline split then its
+ // previous ib-split sibling is a block, and the previous ib-split
+ // sibling of _that_ is an inline, just like aFrame. Similarly, if
+ // aFrame is the first continuation of a block part of an
+ // block-in-inline split (a block-in-inline wrapper block), then its
+ // previous ib-split sibling is an inline and the previous ib-split
+ // sibling of that is either another block-in-inline wrapper block box
+ // or null.
+ nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
+ if (!prevContinuation &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // We're the first continuation, so we can just get the frame
+ // property directly
+ prevContinuation =
+ aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ if (prevContinuation) {
+ prevContinuation =
+ prevContinuation->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ }
+ }
+
+ NS_ASSERTION(!prevContinuation ||
+ prevContinuation->GetContent() == aFrame->GetContent(),
+ "unexpected content mismatch");
+
+ return prevContinuation;
+}
+
+/**
+ * Get the previous continuation or similar ib-split sibling (assuming
+ * block/inline alternation), conditionally on it having the same style.
+ * This assumes that we're not between resolving the two (i.e., that
+ * they're both already resolved.
+ */
+static nsIFrame*
+GetPrevContinuationWithSameStyle(nsIFrame* aFrame)
+{
+ nsIFrame* prevContinuation = GetPrevContinuationWithPossiblySameStyle(aFrame);
+ if (!prevContinuation) {
+ return nullptr;
+ }
+
+ nsStyleContext* prevStyle = prevContinuation->StyleContext();
+ nsStyleContext* selfStyle = aFrame->StyleContext();
+ if (prevStyle != selfStyle) {
+ NS_ASSERTION(prevStyle->GetPseudo() != selfStyle->GetPseudo() ||
+ prevStyle->GetParent() != selfStyle->GetParent(),
+ "continuations should have the same style context");
+ prevContinuation = nullptr;
+ }
+ return prevContinuation;
+}
+
+nsresult
+RestyleManager::ReparentStyleContext(nsIFrame* aFrame)
+{
+ nsIAtom* frameType = aFrame->GetType();
+ if (frameType == nsGkAtoms::placeholderFrame) {
+ // Also reparent the out-of-flow and all its continuations.
+ nsIFrame* outOfFlow =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ NS_ASSERTION(outOfFlow, "no out-of-flow frame");
+ do {
+ ReparentStyleContext(outOfFlow);
+ } while ((outOfFlow = outOfFlow->GetNextContinuation()));
+ } else if (frameType == nsGkAtoms::backdropFrame) {
+ // Style context of backdrop frame has no parent style context, and
+ // thus we do not need to reparent it.
+ return NS_OK;
+ }
+
+ // DO NOT verify the style tree before reparenting. The frame
+ // tree has already been changed, so this check would just fail.
+ nsStyleContext* oldContext = aFrame->StyleContext();
+
+ RefPtr<nsStyleContext> newContext;
+ nsIFrame* providerFrame;
+ nsStyleContext* newParentContext = aFrame->GetParentStyleContext(&providerFrame);
+ bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
+ nsIFrame* providerChild = nullptr;
+ if (isChild) {
+ ReparentStyleContext(providerFrame);
+ // Get the style context again after ReparentStyleContext() which might have
+ // changed it.
+ newParentContext = providerFrame->StyleContext();
+ providerChild = providerFrame;
+ }
+ NS_ASSERTION(newParentContext, "Reparenting something that has no usable"
+ " parent? Shouldn't happen!");
+ // XXX need to do something here to produce the correct style context for
+ // an IB split whose first inline part is inside a first-line frame.
+ // Currently the first IB anonymous block's style context takes the first
+ // part's style context as parent, which is wrong since first-line style
+ // should not apply to the anonymous block.
+
+#ifdef DEBUG
+ {
+ // Check that our assumption that continuations of the same
+ // pseudo-type and with the same style context parent have the
+ // same style context is valid before the reresolution. (We need
+ // to check the pseudo-type and style context parent because of
+ // :first-letter and :first-line, where we create styled and
+ // unstyled letter/line frames distinguished by pseudo-type, and
+ // then need to distinguish their descendants based on having
+ // different parents.)
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+ if (nextContinuation) {
+ nsStyleContext* nextContinuationContext =
+ nextContinuation->StyleContext();
+ NS_ASSERTION(oldContext == nextContinuationContext ||
+ oldContext->GetPseudo() !=
+ nextContinuationContext->GetPseudo() ||
+ oldContext->GetParent() !=
+ nextContinuationContext->GetParent(),
+ "continuations should have the same style context");
+ }
+ }
+#endif
+
+ nsIFrame* prevContinuation =
+ GetPrevContinuationWithPossiblySameStyle(aFrame);
+ nsStyleContext* prevContinuationContext;
+ bool copyFromContinuation =
+ prevContinuation &&
+ (prevContinuationContext = prevContinuation->StyleContext())
+ ->GetPseudo() == oldContext->GetPseudo() &&
+ prevContinuationContext->GetParent() == newParentContext;
+ if (copyFromContinuation) {
+ // Just use the style context from the frame's previous
+ // continuation (see assertion about aFrame->GetNextContinuation()
+ // above, which we would have previously hit for aFrame's previous
+ // continuation).
+ newContext = prevContinuationContext;
+ } else {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ Element* element =
+ ElementForStyleContext(parentFrame ? parentFrame->GetContent() : nullptr,
+ aFrame,
+ oldContext->GetPseudoType());
+ newContext = StyleSet()->
+ ReparentStyleContext(oldContext, newParentContext, element);
+ }
+
+ if (newContext) {
+ if (newContext != oldContext) {
+ // We probably don't want to initiate transitions from
+ // ReparentStyleContext, since we call it during frame
+ // construction rather than in response to dynamic changes.
+ // Also see the comment at the start of
+ // nsTransitionManager::ConsiderInitiatingTransition.
+#if 0
+ if (!copyFromContinuation) {
+ TryInitiatingTransition(mPresContext, aFrame->GetContent(),
+ oldContext, &newContext);
+ }
+#endif
+
+ // Make sure to call CalcStyleDifference so that the new context ends
+ // up resolving all the structs the old context resolved.
+ if (!copyFromContinuation) {
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ DebugOnly<nsChangeHint> styleChange =
+ oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ // The style change is always 0 because we have the same rulenode and
+ // CalcStyleDifference optimizes us away. That's OK, though:
+ // reparenting should never trigger a frame reconstruct, and whenever
+ // it's happening we already plan to reflow and repaint the frames.
+ NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+ "Our frame tree is likely to be bogus!");
+ }
+
+ aFrame->SetStyleContext(newContext);
+
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // only do frames that are in flow
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ child != providerChild) {
+#ifdef DEBUG
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
+
+ NS_ASSERTION(outOfFlowFrame != providerChild,
+ "Out of flow provider?");
+ }
+#endif
+ ReparentStyleContext(child);
+ }
+ }
+ }
+
+ // If this frame is part of an IB split, then the style context of
+ // the next part of the split might be a child of our style context.
+ // Reparent its style context just in case one of our ancestors
+ // (split or not) hasn't done so already). It's not a problem to
+ // reparent the same frame twice because the "if (newContext !=
+ // oldContext)" check will prevent us from redoing work.
+ if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) &&
+ !aFrame->GetPrevContinuation()) {
+ nsIFrame* sib =
+ aFrame->Properties().Get(nsIFrame::IBSplitSibling());
+ if (sib) {
+ ReparentStyleContext(sib);
+ }
+ }
+
+ // do additional contexts
+ int32_t contextIndex = 0;
+ for (nsStyleContext* oldExtraContext;
+ (oldExtraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ RefPtr<nsStyleContext> newExtraContext;
+ newExtraContext = StyleSet()->
+ ReparentStyleContext(oldExtraContext,
+ newContext, nullptr);
+ if (newExtraContext) {
+ if (newExtraContext != oldExtraContext) {
+ // Make sure to call CalcStyleDifference so that the new
+ // context ends up resolving all the structs the old context
+ // resolved.
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ DebugOnly<nsChangeHint> styleChange =
+ oldExtraContext->CalcStyleDifference(newExtraContext,
+ nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ // The style change is always 0 because we have the same
+ // rulenode and CalcStyleDifference optimizes us away. That's
+ // OK, though: reparenting should never trigger a frame
+ // reconstruct, and whenever it's happening we already plan to
+ // reflow and repaint the frames.
+ NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+ "Our frame tree is likely to be bogus!");
+ }
+
+ aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext);
+ }
+ }
+#ifdef DEBUG
+ DebugVerifyStyleTree(aFrame);
+#endif
+ }
+ }
+
+ return NS_OK;
+}
+
+ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>&
+ aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>&
+ aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+ : mPresContext(aPresContext)
+ , mFrame(aFrame)
+ , mParentContent(nullptr)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aChangeList)
+ , mHintsHandled(aHintsHandledByAncestors &
+ ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+ , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aRestyleTracker)
+ , mSelectorsForDescendants(aSelectorsForDescendants)
+ , mTreeMatchContext(aTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aContextsToClear)
+ , mSwappedStructOwners(aSwappedStructOwners)
+ , mIsRootOfRestyle(true)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(eSendAllNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aRestyleTracker.LoggingDepth() + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+}
+
+ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler,
+ nsIFrame* aFrame,
+ uint32_t aConstructorFlags)
+ : mPresContext(aParentRestyler.mPresContext)
+ , mFrame(aFrame)
+ , mParentContent(aParentRestyler.mContent)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aParentRestyler.mChangeList)
+ , mHintsHandled(aParentRestyler.mHintsHandled &
+ ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+ , mParentFrameHintsNotHandledForDescendants(
+ aParentRestyler.mHintsNotHandledForDescendants)
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aParentRestyler.mRestyleTracker)
+ , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
+ , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aParentRestyler.mContextsToClear)
+ , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
+ , mIsRootOfRestyle(false)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(aParentRestyler.mKidsDesiredA11yNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+ if (aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) {
+ // Note that the out-of-flow may not be a geometric descendant of
+ // the frame where we started the reresolve. Therefore, even if
+ // mHintsHandled already includes nsChangeHint_AllReflowHints we
+ // don't want to pass that on to the out-of-flow reresolve, since
+ // that can lead to the out-of-flow not getting reflowed when it
+ // should be (eg a reresolve starting at <body> that involves
+ // reflowing the <body> would miss reflowing fixed-pos nodes that
+ // also need reflow). In the cases when the out-of-flow _is_ a
+ // geometric descendant of a frame we already have a reflow hint
+ // for, reflow coalescing should keep us from doing the work twice.
+ mHintsHandled &= ~nsChangeHint_AllReflowHints;
+ }
+}
+
+ElementRestyler::ElementRestyler(ParentContextFromChildFrame,
+ const ElementRestyler& aParentRestyler,
+ nsIFrame* aFrame)
+ : mPresContext(aParentRestyler.mPresContext)
+ , mFrame(aFrame)
+ , mParentContent(aParentRestyler.mParentContent)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aParentRestyler.mChangeList)
+ , mHintsHandled(aParentRestyler.mHintsHandled &
+ ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+ , mParentFrameHintsNotHandledForDescendants(
+ // assume the worst
+ nsChangeHint_Hints_NotHandledForDescendants)
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aParentRestyler.mRestyleTracker)
+ , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
+ , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aParentRestyler.mContextsToClear)
+ , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
+ , mIsRootOfRestyle(false)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(aParentRestyler.mDesiredA11yNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+}
+
+ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>&
+ aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+ : mPresContext(aPresContext)
+ , mFrame(nullptr)
+ , mParentContent(nullptr)
+ , mContent(aContent)
+ , mChangeList(aChangeList)
+ , mHintsHandled(aHintsHandledByAncestors &
+ ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+ , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aRestyleTracker)
+ , mSelectorsForDescendants(aSelectorsForDescendants)
+ , mTreeMatchContext(aTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aContextsToClear)
+ , mSwappedStructOwners(aSwappedStructOwners)
+ , mIsRootOfRestyle(true)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(eSendAllNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
+#endif
+{
+}
+
+void
+ElementRestyler::AddLayerChangesForAnimation()
+{
+ uint64_t frameGeneration =
+ RestyleManager::GetAnimationGenerationForFrame(mFrame);
+
+ nsChangeHint hint = nsChangeHint(0);
+ for (const LayerAnimationInfo::Record& layerInfo :
+ LayerAnimationInfo::sRecords) {
+ Layer* layer =
+ FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo.mLayerType);
+ if (layer && frameGeneration != layer->GetAnimationGeneration()) {
+ // If we have a transform layer but don't have any transform style, we
+ // probably just removed the transform but haven't destroyed the layer
+ // yet. In this case we will add the appropriate change hint
+ // (nsChangeHint_UpdateContainingBlock) when we compare style contexts
+ // so we can skip adding any change hint here. (If we *were* to add
+ // nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would
+ // complain that we're updating a transform layer without a transform).
+ if (layerInfo.mLayerType == nsDisplayItem::TYPE_TRANSFORM &&
+ !mFrame->StyleDisplay()->HasTransformStyle()) {
+ continue;
+ }
+ hint |= layerInfo.mChangeHint;
+ }
+
+ // We consider it's the first paint for the frame if we have an animation
+ // for the property but have no layer.
+ // Note that in case of animations which has properties preventing running
+ // on the compositor, e.g., width or height, corresponding layer is not
+ // created at all, but even in such cases, we normally set valid change
+ // hint for such animations in each tick, i.e. restyles in each tick. As
+ // a result, we usually do restyles for such animations in every tick on
+ // the main-thread. The only animations which will be affected by this
+ // explicit change hint are animations that have opacity/transform but did
+ // not have those properies just before. e.g, setting transform by
+ // setKeyframes or changing target element from other target which prevents
+ // running on the compositor, etc.
+ if (!layer &&
+ nsLayoutUtils::HasEffectiveAnimation(mFrame, layerInfo.mProperty)) {
+ hint |= layerInfo.mChangeHint;
+ }
+ }
+ if (hint) {
+ mChangeList->AppendChange(mFrame, mContent, hint);
+ }
+}
+
+void
+ElementRestyler::CaptureChange(nsStyleContext* aOldContext,
+ nsStyleContext* aNewContext,
+ nsChangeHint aChangeToAssume,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs)
+{
+ static_assert(nsStyleStructID_Length <= 32,
+ "aEqualStructs is not big enough");
+
+ // Check some invariants about replacing one style context with another.
+ NS_ASSERTION(aOldContext->GetPseudo() == aNewContext->GetPseudo(),
+ "old and new style contexts should have the same pseudo");
+ NS_ASSERTION(aOldContext->GetPseudoType() == aNewContext->GetPseudoType(),
+ "old and new style contexts should have the same pseudo");
+
+ nsChangeHint ourChange =
+ aOldContext->CalcStyleDifference(aNewContext,
+ mParentFrameHintsNotHandledForDescendants,
+ aEqualStructs,
+ aSamePointerStructs);
+ NS_ASSERTION(!(ourChange & nsChangeHint_AllReflowHints) ||
+ (ourChange & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ LOG_RESTYLE("CaptureChange, ourChange = %s, aChangeToAssume = %s",
+ RestyleManager::ChangeHintToString(ourChange).get(),
+ RestyleManager::ChangeHintToString(aChangeToAssume).get());
+ LOG_RESTYLE_INDENT();
+
+ // nsChangeHint_UpdateEffects is inherited, but it can be set due to changes
+ // in inherited properties (fill and stroke). Avoid propagating it into
+ // text nodes.
+ if ((ourChange & nsChangeHint_UpdateEffects) &&
+ mContent && !mContent->IsElement()) {
+ ourChange &= ~nsChangeHint_UpdateEffects;
+ }
+
+ ourChange |= aChangeToAssume;
+ if (!NS_IsHintSubset(ourChange, mHintsHandled)) {
+ mHintsHandled |= ourChange;
+ if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) {
+ LOG_RESTYLE("appending change %s",
+ RestyleManager::ChangeHintToString(ourChange).get());
+ mChangeList->AppendChange(mFrame, mContent, ourChange);
+ } else {
+ LOG_RESTYLE("change has already been handled");
+ }
+ }
+ mHintsNotHandledForDescendants |=
+ NS_HintsNotHandledForDescendantsIn(ourChange);
+ LOG_RESTYLE("mHintsNotHandledForDescendants = %s",
+ RestyleManager::ChangeHintToString(mHintsNotHandledForDescendants).get());
+}
+
+class MOZ_RAII AutoSelectorArrayTruncater final
+{
+public:
+ explicit AutoSelectorArrayTruncater(
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants)
+ : mSelectorsForDescendants(aSelectorsForDescendants)
+ , mOriginalLength(aSelectorsForDescendants.Length())
+ {
+ }
+
+ ~AutoSelectorArrayTruncater()
+ {
+ mSelectorsForDescendants.TruncateLength(mOriginalLength);
+ }
+
+private:
+ nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
+ size_t mOriginalLength;
+};
+
+/**
+ * Called when we are stopping a restyle with eRestyle_SomeDescendants, to
+ * search for descendants that match any of the selectors in
+ * mSelectorsForDescendants. If the element does match one of the selectors,
+ * we cause it to be restyled with eRestyle_Self.
+ *
+ * We traverse down the frame tree (and through the flattened content tree
+ * when we find undisplayed content) unless we find an element that (a) already
+ * has a pending restyle, or (b) does not have a pending restyle but does match
+ * one of the selectors in mSelectorsForDescendants. For (a), we add the
+ * current mSelectorsForDescendants into the existing restyle data, and for (b)
+ * we add a new pending restyle with that array. So in both cases, when we
+ * come to restyling this element back up in ProcessPendingRestyles, we will
+ * again find the eRestyle_SomeDescendants hint and its selectors array.
+ *
+ * This ensures that we don't visit descendant elements and check them
+ * against mSelectorsForDescendants more than once.
+ */
+void
+ElementRestyler::ConditionallyRestyleChildren()
+{
+ MOZ_ASSERT(mContent == mFrame->GetContent());
+
+ if (!mContent->IsElement() || mSelectorsForDescendants.IsEmpty()) {
+ return;
+ }
+
+ Element* element = mContent->AsElement();
+
+ LOG_RESTYLE("traversing descendants of frame %s (with element %s) to "
+ "propagate eRestyle_SomeDescendants for these %d selectors:",
+ FrameTagToString(mFrame).get(),
+ ElementTagToString(element).get(),
+ int(mSelectorsForDescendants.Length()));
+ LOG_RESTYLE_INDENT();
+#ifdef RESTYLE_LOGGING
+ for (nsCSSSelector* sel : mSelectorsForDescendants) {
+ LOG_RESTYLE("%s", sel->RestrictedSelectorToString().get());
+ }
+#endif
+
+ Element* restyleRoot = mRestyleTracker.FindClosestRestyleRoot(element);
+ ConditionallyRestyleChildren(mFrame, restyleRoot);
+}
+
+void
+ElementRestyler::ConditionallyRestyleChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+ MOZ_ASSERT(aFrame->GetContent()->IsElement());
+ MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
+
+ ConditionallyRestyleUndisplayedDescendants(aFrame, aRestyleRoot);
+ ConditionallyRestyleContentChildren(aFrame, aRestyleRoot);
+}
+
+// The structure of this method parallels RestyleContentChildren.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleContentChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+ MOZ_ASSERT(aFrame->GetContent()->IsElement());
+ MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
+
+ if (aFrame->GetContent()->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aFrame->GetContent()->AsElement();
+ }
+
+ for (nsIFrame* f = aFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ nsIFrame::ChildListIterator lists(f);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Out-of-flows are reached through their placeholders. Continuations
+ // and block-in-inline splits are reached through those chains.
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ !GetPrevContinuationWithSameStyle(child)) {
+ // only do frames that are in flow
+ if (child->GetType() == nsGkAtoms::placeholderFrame) { // placeholder
+ // get out of flow frame and recur there
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+
+ // |nsFrame::GetParentStyleContext| checks being out
+ // of flow so that this works correctly.
+ do {
+ if (GetPrevContinuationWithSameStyle(outOfFlowFrame)) {
+ continue;
+ }
+ if (!ConditionallyRestyle(outOfFlowFrame, aRestyleRoot)) {
+ ConditionallyRestyleChildren(outOfFlowFrame, aRestyleRoot);
+ }
+ } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
+ } else { // regular child frame
+ if (child != mResolvedChild) {
+ if (!ConditionallyRestyle(child, aRestyleRoot)) {
+ ConditionallyRestyleChildren(child, aRestyleRoot);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// The structure of this method parallels RestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleUndisplayedDescendants(
+ nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(aFrame, undisplayedParent)) {
+ DoConditionallyRestyleUndisplayedDescendants(undisplayedParent,
+ aRestyleRoot);
+ }
+}
+
+// The structure of this method parallels DoRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::DoConditionallyRestyleUndisplayedDescendants(
+ nsIContent* aParent,
+ Element* aRestyleRoot)
+{
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ UndisplayedNode* nodes = fc->GetAllUndisplayedContentIn(aParent);
+ ConditionallyRestyleUndisplayedNodes(nodes, aParent,
+ StyleDisplay::None, aRestyleRoot);
+ nodes = fc->GetAllDisplayContentsIn(aParent);
+ ConditionallyRestyleUndisplayedNodes(nodes, aParent,
+ StyleDisplay::Contents, aRestyleRoot);
+}
+
+// The structure of this method parallels RestyleUndisplayedNodes.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleUndisplayedNodes(
+ UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ const StyleDisplay aDisplay,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aDisplay == StyleDisplay::None ||
+ aDisplay == StyleDisplay::Contents);
+ if (!aUndisplayed) {
+ return;
+ }
+
+ if (aUndisplayedParent &&
+ aUndisplayedParent->IsElement() &&
+ aUndisplayedParent->HasFlag(mRestyleTracker.RootBit())) {
+ MOZ_ASSERT(!aUndisplayedParent->IsStyledByServo());
+ aRestyleRoot = aUndisplayedParent->AsElement();
+ }
+
+ for (UndisplayedNode* undisplayed = aUndisplayed; undisplayed;
+ undisplayed = undisplayed->mNext) {
+
+ if (!undisplayed->mContent->IsElement()) {
+ continue;
+ }
+
+ Element* element = undisplayed->mContent->AsElement();
+
+ if (!ConditionallyRestyle(element, aRestyleRoot)) {
+ if (aDisplay == StyleDisplay::None) {
+ ConditionallyRestyleContentDescendants(element, aRestyleRoot);
+ } else { // StyleDisplay::Contents
+ DoConditionallyRestyleUndisplayedDescendants(element, aRestyleRoot);
+ }
+ }
+ }
+}
+
+void
+ElementRestyler::ConditionallyRestyleContentDescendants(Element* aElement,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(!aElement->IsStyledByServo());
+ if (aElement->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aElement;
+ }
+
+ FlattenedChildIterator it(aElement);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsElement()) {
+ Element* e = n->AsElement();
+ if (!ConditionallyRestyle(e, aRestyleRoot)) {
+ ConditionallyRestyleContentDescendants(e, aRestyleRoot);
+ }
+ }
+ }
+}
+
+bool
+ElementRestyler::ConditionallyRestyle(nsIFrame* aFrame, Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+
+ if (!aFrame->GetContent()->IsElement()) {
+ return true;
+ }
+
+ return ConditionallyRestyle(aFrame->GetContent()->AsElement(), aRestyleRoot);
+}
+
+bool
+ElementRestyler::ConditionallyRestyle(Element* aElement, Element* aRestyleRoot)
+{
+ MOZ_ASSERT(!aElement->IsStyledByServo());
+ LOG_RESTYLE("considering element %s for eRestyle_SomeDescendants",
+ ElementTagToString(aElement).get());
+ LOG_RESTYLE_INDENT();
+
+ if (aElement->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aElement;
+ }
+
+ if (mRestyleTracker.HasRestyleData(aElement)) {
+ nsRestyleHint rshint = eRestyle_SomeDescendants;
+ if (SelectorMatchesForRestyle(aElement)) {
+ LOG_RESTYLE("element has existing restyle data and matches a selector");
+ rshint |= eRestyle_Self;
+ } else {
+ LOG_RESTYLE("element has existing restyle data but doesn't match selectors");
+ }
+ RestyleHintData data;
+ data.mSelectorsForDescendants = mSelectorsForDescendants;
+ mRestyleTracker.AddPendingRestyle(aElement, rshint, nsChangeHint(0), &data,
+ Some(aRestyleRoot));
+ return true;
+ }
+
+ if (SelectorMatchesForRestyle(aElement)) {
+ LOG_RESTYLE("element has no restyle data but matches a selector");
+ RestyleHintData data;
+ data.mSelectorsForDescendants = mSelectorsForDescendants;
+ mRestyleTracker.AddPendingRestyle(aElement,
+ eRestyle_Self | eRestyle_SomeDescendants,
+ nsChangeHint(0), &data,
+ Some(aRestyleRoot));
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ElementRestyler::MustCheckUndisplayedContent(nsIFrame* aFrame,
+ nsIContent*& aUndisplayedParent)
+{
+ // When the root element is display:none, we still construct *some*
+ // frames that have the root element as their mContent, down to the
+ // DocElementContainingBlock.
+ if (aFrame->StyleContext()->GetPseudo()) {
+ aUndisplayedParent = nullptr;
+ return aFrame == mPresContext->FrameConstructor()->
+ GetDocElementContainingBlock();
+ }
+
+ aUndisplayedParent = aFrame->GetContent();
+ return !!aUndisplayedParent;
+}
+
+/**
+ * Helper for MoveStyleContextsForChildren, below. Appends the style
+ * contexts to be moved to mFrame's current (new) style context to
+ * aContextsToMove.
+ */
+bool
+ElementRestyler::MoveStyleContextsForContentChildren(
+ nsIFrame* aParent,
+ nsStyleContext* aOldContext,
+ nsTArray<nsStyleContext*>& aContextsToMove)
+{
+ nsIFrame::ChildListIterator lists(aParent);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Bail out if we have out-of-flow frames.
+ // FIXME: It might be safe to just continue here instead of bailing out.
+ if (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ return false;
+ }
+ if (GetPrevContinuationWithSameStyle(child)) {
+ continue;
+ }
+ // Bail out if we have placeholder frames.
+ // FIXME: It is probably safe to just continue here instead of bailing out.
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ return false;
+ }
+ nsStyleContext* sc = child->StyleContext();
+ if (sc->GetParent() != aOldContext) {
+ return false;
+ }
+ nsIAtom* type = child->GetType();
+ if (type == nsGkAtoms::letterFrame ||
+ type == nsGkAtoms::lineFrame) {
+ return false;
+ }
+ if (sc->HasChildThatUsesGrandancestorStyle()) {
+ // XXX Not sure if we need this?
+ return false;
+ }
+ nsIAtom* pseudoTag = sc->GetPseudo();
+ if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ return false;
+ }
+ aContextsToMove.AppendElement(sc);
+ }
+ }
+ return true;
+}
+
+/**
+ * Traverses to child elements (through the current frame's same style
+ * continuations, just like RestyleChildren does) and moves any style context
+ * for those children to be parented under mFrame's current (new) style
+ * context.
+ *
+ * False is returned if it encounters any conditions on the child elements'
+ * frames and style contexts that means it is impossible to move a
+ * style context. If false is returned, no style contexts will have been
+ * moved.
+ */
+bool
+ElementRestyler::MoveStyleContextsForChildren(nsStyleContext* aOldContext)
+{
+ // Bail out if there are undisplayed or display:contents children.
+ // FIXME: We could get this to work if we need to.
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(mFrame, undisplayedParent)) {
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ if (fc->GetAllUndisplayedContentIn(undisplayedParent) ||
+ fc->GetAllDisplayContentsIn(undisplayedParent)) {
+ return false;
+ }
+ }
+
+ nsTArray<nsStyleContext*> contextsToMove;
+
+ MOZ_ASSERT(!MustReframeForBeforePseudo(),
+ "shouldn't need to reframe ::before as we would have had "
+ "eRestyle_Subtree and wouldn't get in here");
+
+ DebugOnly<nsIFrame*> lastContinuation;
+ for (nsIFrame* f = mFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ lastContinuation = f;
+ if (!MoveStyleContextsForContentChildren(f, aOldContext, contextsToMove)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!MustReframeForAfterPseudo(lastContinuation),
+ "shouldn't need to reframe ::after as we would have had "
+ "eRestyle_Subtree and wouldn't get in here");
+
+ nsStyleContext* newParent = mFrame->StyleContext();
+ for (nsStyleContext* child : contextsToMove) {
+ // We can have duplicate entries in contextsToMove, so only move
+ // each style context once.
+ if (child->GetParent() != newParent) {
+ child->MoveTo(newParent);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Recompute style for mFrame (which should not have a prev continuation
+ * with the same style), all of its next continuations with the same
+ * style, and all ib-split siblings of the same type (either block or
+ * inline, skipping the intermediates of the other type) and accumulate
+ * changes into mChangeList given that mHintsHandled is already accumulated
+ * for an ancestor.
+ * mParentContent is the content node used to resolve the parent style
+ * context. This means that, for pseudo-elements, it is the content
+ * that should be used for selector matching (rather than the fake
+ * content node attached to the frame).
+ */
+void
+ElementRestyler::Restyle(nsRestyleHint aRestyleHint)
+{
+ // It would be nice if we could make stronger assertions here; they
+ // would let us simplify the ?: expressions below setting |content|
+ // and |pseudoContent| in sensible ways as well as making what
+ // |content| and |pseudoContent| mean, and their relationship to
+ // |mFrame->GetContent()|, make more sense. However, we can't,
+ // because of frame trees like the one in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=472353#c14 . Once we
+ // fix bug 242277 we should be able to make this make more sense.
+ NS_ASSERTION(mFrame->GetContent() || !mParentContent ||
+ !mParentContent->GetParent(),
+ "frame must have content (unless at the top of the tree)");
+ MOZ_ASSERT(mPresContext == mFrame->PresContext(), "pres contexts match");
+
+ NS_ASSERTION(!GetPrevContinuationWithSameStyle(mFrame),
+ "should not be trying to restyle this frame separately");
+
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
+ "eRestyle_LaterSiblings must not be part of aRestyleHint");
+
+ mPresContext->RestyledElement();
+
+ AutoDisplayContentsAncestorPusher adcp(mTreeMatchContext, mPresContext,
+ mFrame->GetContent() ? mFrame->GetContent()->GetParent() : nullptr);
+
+ AutoSelectorArrayTruncater asat(mSelectorsForDescendants);
+
+ // List of descendant elements of mContent we know we will eventually need to
+ // restyle. Before we return from this function, we call
+ // RestyleTracker::AddRestyleRootsIfAwaitingRestyle to ensure they get
+ // restyled in RestyleTracker::DoProcessRestyles.
+ nsTArray<RefPtr<Element>> descendants;
+
+ nsRestyleHint hintToRestore = nsRestyleHint(0);
+ RestyleHintData hintDataToRestore;
+ if (mContent && mContent->IsElement() &&
+ // If we're resolving from the root of the frame tree (which
+ // we do when mDoRebuildAllStyleData), we need to avoid getting the
+ // root's restyle data until we get to its primary frame, since
+ // it's the primary frame that has the styles for the root element
+ // (rather than the ancestors of the primary frame whose mContent
+ // is the root node but which have different styles). If we use
+ // up the hint for one of the ancestors that we hit first, then
+ // we'll fail to do the restyling we need to do.
+ // Likewise, if we're restyling something with two nested frames,
+ // and we post a restyle from the transition manager while
+ // computing style for the outer frame (to be computed after the
+ // descendants have been resolved), we don't want to consume it
+ // for the inner frame.
+ mContent->GetPrimaryFrame() == mFrame) {
+ mContent->OwnerDoc()->FlushPendingLinkUpdates();
+ nsAutoPtr<RestyleTracker::RestyleData> restyleData;
+ if (mRestyleTracker.GetRestyleData(mContent->AsElement(), restyleData)) {
+ if (!NS_IsHintSubset(restyleData->mChangeHint, mHintsHandled)) {
+ mHintsHandled |= restyleData->mChangeHint;
+ mChangeList->AppendChange(mFrame, mContent, restyleData->mChangeHint);
+ }
+ mSelectorsForDescendants.AppendElements(
+ restyleData->mRestyleHintData.mSelectorsForDescendants);
+ hintToRestore = restyleData->mRestyleHint;
+ hintDataToRestore = Move(restyleData->mRestyleHintData);
+ aRestyleHint = nsRestyleHint(aRestyleHint | restyleData->mRestyleHint);
+ descendants.SwapElements(restyleData->mDescendants);
+ }
+ }
+
+ // If we are restyling this frame with eRestyle_Self or weaker hints,
+ // we restyle children with nsRestyleHint(0). But we pass the
+ // eRestyle_ForceDescendants flag down too.
+ nsRestyleHint childRestyleHint =
+ nsRestyleHint(aRestyleHint & (eRestyle_SomeDescendants |
+ eRestyle_Subtree |
+ eRestyle_ForceDescendants));
+
+ RefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
+
+ nsTArray<SwapInstruction> swaps;
+
+ // TEMPORARY (until bug 918064): Call RestyleSelf for each
+ // continuation or block-in-inline sibling.
+
+ // We must make a single decision on how to process this frame and
+ // its descendants, yet RestyleSelf might return different RestyleResult
+ // values for the different same-style continuations. |result| is our
+ // overall decision.
+ RestyleResult result = RestyleResult::eNone;
+ uint32_t swappedStructs = 0;
+
+ nsRestyleHint thisRestyleHint = aRestyleHint;
+
+ bool haveMoreContinuations = false;
+ for (nsIFrame* f = mFrame; f; ) {
+ RestyleResult thisResult =
+ RestyleSelf(f, thisRestyleHint, &swappedStructs, swaps);
+
+ if (thisResult != RestyleResult::eStop) {
+ // Calls to RestyleSelf for later same-style continuations must not
+ // return RestyleResult::eStop, so pass eRestyle_Force in to them.
+ thisRestyleHint = nsRestyleHint(thisRestyleHint | eRestyle_Force);
+
+ if (result == RestyleResult::eStop) {
+ // We received RestyleResult::eStop for earlier same-style
+ // continuations, and RestyleResult::eStopWithStyleChange or
+ // RestyleResult::eContinue(AndForceDescendants) for this one; go
+ // back and force-restyle the earlier continuations.
+ result = thisResult;
+ f = mFrame;
+ continue;
+ }
+ }
+
+ if (thisResult > result) {
+ // We take the highest RestyleResult value when working out what to do
+ // with this frame and its descendants. Higher RestyleResult values
+ // represent a superset of the work done by lower values.
+ result = thisResult;
+ }
+
+ f = RestyleManager::GetNextContinuationWithSameStyle(f,
+ oldContext,
+ &haveMoreContinuations);
+ }
+
+ // Some changes to animations don't affect the computed style and yet still
+ // require the layer to be updated. For example, pausing an animation via
+ // the Web Animations API won't affect an element's style but still
+ // requires us to pull the animation off the layer.
+ //
+ // Although we only expect this code path to be called when computed style
+ // is not changing, we can sometimes reach this at the end of a transition
+ // when the animated style is being removed. Since
+ // AddLayerChangesForAnimation checks if mFrame has a transform style or not,
+ // we need to call it *after* calling RestyleSelf to ensure the animated
+ // transform has been removed first.
+ AddLayerChangesForAnimation();
+
+ if (haveMoreContinuations && hintToRestore) {
+ // If we have more continuations with different style (e.g., because
+ // we're inside a ::first-letter or ::first-line), put the restyle
+ // hint back.
+ mRestyleTracker.AddPendingRestyleToTable(mContent->AsElement(),
+ hintToRestore, nsChangeHint(0));
+ }
+
+ if (result == RestyleResult::eStop) {
+ MOZ_ASSERT(mFrame->StyleContext() == oldContext,
+ "frame should have been left with its old style context");
+
+ nsIFrame* unused;
+ nsStyleContext* newParent = mFrame->GetParentStyleContext(&unused);
+ if (oldContext->GetParent() != newParent) {
+ // If we received RestyleResult::eStop, then the old style context was
+ // left on mFrame. Since we ended up restyling our parent, change
+ // this old style context to point to its new parent.
+ LOG_RESTYLE("moving style context %p from old parent %p to new parent %p",
+ oldContext.get(), oldContext->GetParent(), newParent);
+ // We keep strong references to the new parent around until the end
+ // of the restyle, in case:
+ // (a) we swapped structs between the old and new parent,
+ // (b) some descendants of the old parent are not getting restyled
+ // (which is the reason for the existence of
+ // ClearCachedInheritedStyleDataOnDescendants),
+ // (c) something under ProcessPendingRestyles (which notably is called
+ // *before* ClearCachedInheritedStyleDataOnDescendants is called
+ // on the old context) causes the new parent to be destroyed, thus
+ // destroying its owned structs, and
+ // (d) something under ProcessPendingRestyles then wants to use of those
+ // now destroyed structs (through the old parent's descendants).
+ mSwappedStructOwners.AppendElement(newParent);
+ oldContext->MoveTo(newParent);
+ }
+
+ // Send the accessibility notifications that RestyleChildren otherwise
+ // would have sent.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+ SendAccessibilityNotifications();
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ ConditionallyRestyleChildren();
+ }
+ return;
+ }
+
+ if (result == RestyleResult::eStopWithStyleChange &&
+ !(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ MOZ_ASSERT(mFrame->StyleContext() != oldContext,
+ "RestyleResult::eStopWithStyleChange should only be returned "
+ "if we got a new style context or we will reconstruct");
+ MOZ_ASSERT(swappedStructs == 0,
+ "should have ensured we didn't swap structs when "
+ "returning RestyleResult::eStopWithStyleChange");
+
+ // We need to ensure that all of the frames that inherit their style
+ // from oldContext are able to be moved across to newContext.
+ // MoveStyleContextsForChildren will check for certain conditions
+ // to ensure it is safe to move all of the relevant child style
+ // contexts to newContext. If these conditions fail, it will
+ // return false, and we'll have to continue restyling.
+ const bool canStop = MoveStyleContextsForChildren(oldContext);
+
+ if (canStop) {
+ // Send the accessibility notifications that RestyleChildren otherwise
+ // would have sent.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+ SendAccessibilityNotifications();
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ ConditionallyRestyleChildren();
+ }
+ return;
+ }
+
+ // Turns out we couldn't stop restyling here. Process the struct
+ // swaps that RestyleSelf would've done had we not returned
+ // RestyleResult::eStopWithStyleChange.
+ for (SwapInstruction& swap : swaps) {
+ LOG_RESTYLE("swapping style structs between %p and %p",
+ swap.mOldContext.get(), swap.mNewContext.get());
+ swap.mOldContext->SwapStyleData(swap.mNewContext, swap.mStructsToSwap);
+ swappedStructs |= swap.mStructsToSwap;
+ }
+ swaps.Clear();
+ }
+
+ if (!swappedStructs) {
+ // If we swapped any structs from the old context, then we need to keep
+ // it alive until after the RestyleChildren call so that we can fix up
+ // its descendants' cached structs.
+ oldContext = nullptr;
+ }
+
+ if (result == RestyleResult::eContinueAndForceDescendants) {
+ childRestyleHint =
+ nsRestyleHint(childRestyleHint | eRestyle_ForceDescendants);
+ }
+
+ // No need to do this if we're planning to reframe already.
+ // It's also important to check mHintsHandled since we use
+ // mFrame->StyleContext(), which is out of date if mHintsHandled
+ // has a ReconstructFrame hint. Using an out of date style
+ // context could trigger assertions about mismatched rule trees.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ RestyleChildren(childRestyleHint);
+ }
+
+ if (oldContext && !oldContext->HasSingleReference()) {
+ // If we swapped some structs out of oldContext in the RestyleSelf call
+ // and after the RestyleChildren call we still have other strong references
+ // to it, we need to make ensure its descendants don't cache any of the
+ // structs that were swapped out.
+ //
+ // Much of the time we will not get in here; we do for example when the
+ // style context is shared with a later IB split sibling (which we won't
+ // restyle until a bit later) or if other code is holding a strong reference
+ // to the style context (as is done by nsTransformedTextRun objects, which
+ // can be referenced by a text frame's mTextRun longer than the frame's
+ // mStyleContext).
+ //
+ // Also, we don't want this style context to get any more uses by being
+ // returned from nsStyleContext::FindChildWithRules, so we add the
+ // NS_STYLE_INELIGIBLE_FOR_SHARING bit to it.
+ oldContext->SetIneligibleForSharing();
+
+ ContextToClear* toClear = mContextsToClear.AppendElement();
+ toClear->mStyleContext = Move(oldContext);
+ toClear->mStructs = swappedStructs;
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+}
+
+/**
+ * Depending on the details of the frame we are restyling or its old style
+ * context, we may or may not be able to stop restyling after this frame if
+ * we find we had no style changes.
+ *
+ * This function returns RestyleResult::eStop if it does not find any
+ * conditions that would preclude stopping restyling, and
+ * RestyleResult::eContinue if it does.
+ */
+void
+ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange)
+{
+ // We can't handle situations where the primary style context of a frame
+ // has not had any style data changes, but its additional style contexts
+ // have, so we don't considering stopping if this frame has any additional
+ // style contexts.
+ if (aSelf->GetAdditionalStyleContext(0)) {
+ LOG_RESTYLE_CONTINUE("there are additional style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // Style changes might have moved children between the two nsLetterFrames
+ // (the one matching ::first-letter and the one containing the rest of the
+ // content). Continue restyling to the children of the nsLetterFrame so
+ // that they get the correct style context parent. Similarly for
+ // nsLineFrames.
+ nsIAtom* type = aSelf->GetType();
+
+ if (type == nsGkAtoms::letterFrame) {
+ LOG_RESTYLE_CONTINUE("frame is a letter frame");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (type == nsGkAtoms::lineFrame) {
+ LOG_RESTYLE_CONTINUE("frame is a line frame");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // Some style computations depend not on the parent's style, but a grandparent
+ // or one the grandparent's ancestors. An example is an explicit 'inherit'
+ // value for align-self, where if the parent frame's value for the property is
+ // 'auto' we end up inheriting the computed value from the grandparent. We
+ // can't stop the restyling process on this frame (the one with 'auto', in
+ // this example), as the grandparent's computed value might have changed
+ // and we need to recompute the child's 'inherit' to that new value.
+ nsStyleContext* oldContext = aSelf->StyleContext();
+ if (oldContext->HasChildThatUsesGrandancestorStyle()) {
+ LOG_RESTYLE_CONTINUE("the old context uses grandancestor style");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // We ignore all situations that involve :visited style.
+ if (oldContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the old style context has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ nsStyleContext* parentContext = oldContext->GetParent();
+ if (parentContext && parentContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the old style context's parent has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // We also ignore frames for pseudos, as their style contexts have
+ // inheritance structures that do not match the frame inheritance
+ // structure. To avoid enumerating and checking all of the cases
+ // where we have this kind of inheritance, we keep restyling past
+ // pseudos.
+ nsIAtom* pseudoTag = oldContext->GetPseudo();
+ if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ LOG_RESTYLE_CONTINUE("the old style context is for a pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ nsIFrame* parent = mFrame->GetParent();
+
+ if (parent) {
+ // Also if the parent has a pseudo, as this frame's style context will
+ // be inheriting from a grandparent frame's style context (or a further
+ // ancestor).
+ nsIAtom* parentPseudoTag = parent->StyleContext()->GetPseudo();
+ if (parentPseudoTag &&
+ parentPseudoTag != nsCSSAnonBoxes::mozOtherNonElement) {
+ MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::mozText,
+ "Style of text node should not be parent of anything");
+ LOG_RESTYLE_CONTINUE("the old style context's parent is for a pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ // Parent style context pseudo-ness doesn't affect whether we can
+ // return RestyleResult::eStopWithStyleChange.
+ //
+ // If we had later conditions to check in this function, we would
+ // continue to check them, in case we set aCanStopWithStyleChange to
+ // false.
+ }
+ }
+}
+
+void
+ElementRestyler::ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
+ nsStyleContext* aNewContext,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange)
+{
+ // If we've already determined that we must continue styling, we don't
+ // need to check anything.
+ if (aRestyleResult == RestyleResult::eContinue && !aCanStopWithStyleChange) {
+ return;
+ }
+
+ // Keep restyling if the new style context has any style-if-visted style, so
+ // that we can avoid the style context tree surgery having to deal to deal
+ // with visited styles.
+ if (aNewContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the new style context has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // If link-related information has changed, or the pseudo for the frame has
+ // changed, or the new style context points to a different rule node, we can't
+ // leave the old style context on the frame.
+ nsStyleContext* oldContext = aSelf->StyleContext();
+ if (oldContext->IsLinkContext() != aNewContext->IsLinkContext() ||
+ oldContext->RelevantLinkVisited() != aNewContext->RelevantLinkVisited() ||
+ oldContext->GetPseudo() != aNewContext->GetPseudo() ||
+ oldContext->GetPseudoType() != aNewContext->GetPseudoType()) {
+ LOG_RESTYLE_CONTINUE("the old and new style contexts have different link/"
+ "visited/pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->RuleNode() != aNewContext->RuleNode()) {
+ LOG_RESTYLE_CONTINUE("the old and new style contexts have different "
+ "rulenodes");
+ aRestyleResult = RestyleResult::eContinue;
+ // Continue to check other conditions if aCanStopWithStyleChange might
+ // still need to be set to false.
+ if (!aCanStopWithStyleChange) {
+ return;
+ }
+ }
+
+ // If the old and new style contexts differ in their
+ // NS_STYLE_HAS_TEXT_DECORATION_LINES or NS_STYLE_HAS_PSEUDO_ELEMENT_DATA
+ // bits, then we must keep restyling so that those new bit values are
+ // propagated.
+ if (oldContext->HasTextDecorationLines() !=
+ aNewContext->HasTextDecorationLines()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_TEXT_DECORATION_LINES differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->HasPseudoElementData() !=
+ aNewContext->HasPseudoElementData()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->ShouldSuppressLineBreak() !=
+ aNewContext->ShouldSuppressLineBreak()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_SUPPRESS_LINEBREAK differs"
+ "between old and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->IsInDisplayNoneSubtree() !=
+ aNewContext->IsInDisplayNoneSubtree()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_IN_DISPLAY_NONE_SUBTREE differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->IsTextCombined() != aNewContext->IsTextCombined()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_IS_TEXT_COMBINED differs between "
+ "old and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+}
+
+bool
+ElementRestyler::SelectorMatchesForRestyle(Element* aElement)
+{
+ if (!aElement) {
+ return false;
+ }
+ for (nsCSSSelector* selector : mSelectorsForDescendants) {
+ if (nsCSSRuleProcessor::RestrictedSelectorMatches(aElement, selector,
+ mTreeMatchContext)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ElementRestyler::MustRestyleSelf(nsRestyleHint aRestyleHint,
+ Element* aElement)
+{
+ return (aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) ||
+ ((aRestyleHint & eRestyle_SomeDescendants) &&
+ SelectorMatchesForRestyle(aElement));
+}
+
+bool
+ElementRestyler::CanReparentStyleContext(nsRestyleHint aRestyleHint)
+{
+ // If we had any restyle hints other than the ones listed below,
+ // which don't control whether the current frame/element needs
+ // a new style context by looking up a new rule node, or if
+ // we are reconstructing the entire rule tree, then we can't
+ // use ReparentStyleContext.
+ return !(aRestyleHint & ~(eRestyle_Force |
+ eRestyle_ForceDescendants |
+ eRestyle_SomeDescendants)) &&
+ !StyleSet()->IsInRuleTreeReconstruct();
+}
+
+// Returns true iff any rule node that is an ancestor-or-self of the
+// two specified rule nodes, but which is not an ancestor of both,
+// has any inherited style data. If false is returned, then we know
+// that a change from one rule node to the other must not result in
+// any change in inherited style data.
+static bool
+CommonInheritedStyleData(nsRuleNode* aRuleNode1, nsRuleNode* aRuleNode2)
+{
+ if (aRuleNode1 == aRuleNode2) {
+ return true;
+ }
+
+ nsRuleNode* n1 = aRuleNode1->GetParent();
+ nsRuleNode* n2 = aRuleNode2->GetParent();
+
+ if (n1 == n2) {
+ // aRuleNode1 and aRuleNode2 sharing a parent is a common case, e.g.
+ // when modifying a style="" attribute. (We must null check GetRule()'s
+ // result since although we know the two parents are the same, it might
+ // be null, as in the case of the two rule nodes being roots of two
+ // different rule trees.)
+ if (aRuleNode1->GetRule() &&
+ aRuleNode1->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ if (aRuleNode2->GetRule() &&
+ aRuleNode2->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ return true;
+ }
+
+ // Compute the depths of aRuleNode1 and aRuleNode2.
+ int d1 = 0, d2 = 0;
+ while (n1) {
+ ++d1;
+ n1 = n1->GetParent();
+ }
+ while (n2) {
+ ++d2;
+ n2 = n2->GetParent();
+ }
+
+ // Make aRuleNode1 be the deeper node.
+ if (d2 > d1) {
+ std::swap(d1, d2);
+ std::swap(aRuleNode1, aRuleNode2);
+ }
+
+ // Check all of the rule nodes in the deeper branch until we reach
+ // the same depth as the shallower branch.
+ n1 = aRuleNode1;
+ n2 = aRuleNode2;
+ while (d1 > d2) {
+ nsIStyleRule* rule = n1->GetRule();
+ MOZ_ASSERT(rule, "non-root rule node should have a rule");
+ if (rule->MightMapInheritedStyleData()) {
+ return false;
+ }
+ n1 = n1->GetParent();
+ --d1;
+ }
+
+ // Check both branches simultaneously until we reach a common ancestor.
+ while (n1 != n2) {
+ MOZ_ASSERT(n1);
+ MOZ_ASSERT(n2);
+ // As above, we must null check GetRule()'s result since we won't find
+ // a common ancestor if the two rule nodes come from different rule trees,
+ // and thus we might reach the root (which has a null rule).
+ if (n1->GetRule() && n1->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ if (n2->GetRule() && n2->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ n1 = n1->GetParent();
+ n2 = n2->GetParent();
+ }
+
+ return true;
+}
+
+ElementRestyler::RestyleResult
+ElementRestyler::RestyleSelf(nsIFrame* aSelf,
+ nsRestyleHint aRestyleHint,
+ uint32_t* aSwappedStructs,
+ nsTArray<SwapInstruction>& aSwaps)
+{
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
+ "eRestyle_LaterSiblings must not be part of aRestyleHint");
+
+ // XXXldb get new context from prev-in-flow if possible, to avoid
+ // duplication. (Or should we just let |GetContext| handle that?)
+ // Getting the hint would be nice too, but that's harder.
+
+ // XXXbryner we may be able to avoid some of the refcounting goop here.
+ // We do need a reference to oldContext for the lifetime of this function, and it's possible
+ // that the frame has the last reference to it, so AddRef it here.
+
+ LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
+ FrameTagToString(aSelf).get(),
+ RestyleManagerBase::RestyleHintToString(aRestyleHint).get());
+ LOG_RESTYLE_INDENT();
+
+ // Initially assume that it is safe to stop restyling.
+ //
+ // Throughout most of this function, we update the following two variables
+ // independently. |result| is set to RestyleResult::eContinue when we
+ // detect a condition that would not allow us to return RestyleResult::eStop.
+ // |canStopWithStyleChange| is set to false when we detect a condition
+ // that would not allow us to return RestyleResult::eStopWithStyleChange.
+ //
+ // Towards the end of this function, we reconcile these two variables --
+ // if |canStopWithStyleChange| is true, we convert |result| into
+ // RestyleResult::eStopWithStyleChange.
+ RestyleResult result = RestyleResult::eStop;
+ bool canStopWithStyleChange = true;
+
+ if (aRestyleHint & ~eRestyle_SomeDescendants) {
+ // If we are doing any restyling of the current element, or if we're
+ // forced to continue, we must.
+ result = RestyleResult::eContinue;
+
+ // If we have to restyle children, we can't return
+ // RestyleResult::eStopWithStyleChange.
+ if (aRestyleHint & (eRestyle_Subtree | eRestyle_Force |
+ eRestyle_ForceDescendants)) {
+ canStopWithStyleChange = false;
+ }
+ }
+
+ // We only consider returning RestyleResult::eStopWithStyleChange if this
+ // is the root of the restyle. (Otherwise, we would need to track the
+ // style changes of the ancestors we just restyled.)
+ if (!mIsRootOfRestyle) {
+ canStopWithStyleChange = false;
+ }
+
+ // Look at the frame and its current style context for conditions
+ // that would change our RestyleResult.
+ ComputeRestyleResultFromFrame(aSelf, result, canStopWithStyleChange);
+
+ nsChangeHint assumeDifferenceHint = nsChangeHint(0);
+ RefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
+ nsStyleSet* styleSet = StyleSet();
+
+#ifdef ACCESSIBILITY
+ mWasFrameVisible = nsIPresShell::IsAccessibilityActive() ?
+ oldContext->StyleVisibility()->IsVisible() : false;
+#endif
+
+ nsIAtom* const pseudoTag = oldContext->GetPseudo();
+ const CSSPseudoElementType pseudoType = oldContext->GetPseudoType();
+
+ // Get the frame providing the parent style context. If it is a
+ // child, then resolve the provider first.
+ nsIFrame* providerFrame;
+ nsStyleContext* parentContext = aSelf->GetParentStyleContext(&providerFrame);
+ bool isChild = providerFrame && providerFrame->GetParent() == aSelf;
+ if (isChild) {
+ MOZ_ASSERT(providerFrame->GetContent() == aSelf->GetContent(),
+ "Postcondition for GetParentStyleContext() violated. "
+ "That means we need to add the current element to the "
+ "ancestor filter.");
+
+ // resolve the provider here (before aSelf below).
+ LOG_RESTYLE("resolving child provider frame");
+
+ // assumeDifferenceHint forces the parent's change to be also
+ // applied to this frame, no matter what
+ // nsStyleContext::CalcStyleDifference says. CalcStyleDifference
+ // can't be trusted because it assumes any changes to the parent
+ // style context provider will be automatically propagated to
+ // the frame(s) with child style contexts.
+
+ ElementRestyler providerRestyler(PARENT_CONTEXT_FROM_CHILD_FRAME,
+ *this, providerFrame);
+ providerRestyler.Restyle(aRestyleHint);
+ assumeDifferenceHint = providerRestyler.HintsHandledForFrame();
+
+ // The provider's new context becomes the parent context of
+ // aSelf's context.
+ parentContext = providerFrame->StyleContext();
+ // Set |mResolvedChild| so we don't bother resolving the
+ // provider again.
+ mResolvedChild = providerFrame;
+ LOG_RESTYLE_CONTINUE("we had a provider frame");
+ // Continue restyling past the odd style context inheritance.
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+
+ if (providerFrame != aSelf->GetParent()) {
+ // We don't actually know what the parent style context's
+ // non-inherited hints were, so assume the worst.
+ mParentFrameHintsNotHandledForDescendants =
+ nsChangeHint_Hints_NotHandledForDescendants;
+ }
+
+ LOG_RESTYLE("parentContext = %p", parentContext);
+
+ // do primary context
+ RefPtr<nsStyleContext> newContext;
+ nsIFrame* prevContinuation =
+ GetPrevContinuationWithPossiblySameStyle(aSelf);
+ nsStyleContext* prevContinuationContext;
+ bool copyFromContinuation =
+ prevContinuation &&
+ (prevContinuationContext = prevContinuation->StyleContext())
+ ->GetPseudo() == oldContext->GetPseudo() &&
+ prevContinuationContext->GetParent() == parentContext;
+ if (copyFromContinuation) {
+ // Just use the style context from the frame's previous
+ // continuation.
+ LOG_RESTYLE("using previous continuation's context");
+ newContext = prevContinuationContext;
+ } else if (pseudoTag == nsCSSAnonBoxes::mozText) {
+ MOZ_ASSERT(aSelf->GetType() == nsGkAtoms::textFrame);
+ newContext =
+ styleSet->ResolveStyleForText(aSelf->GetContent(), parentContext);
+ } else if (nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ newContext = styleSet->ResolveStyleForOtherNonElement(parentContext);
+ }
+ else {
+ Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
+ if (!MustRestyleSelf(aRestyleHint, element)) {
+ if (CanReparentStyleContext(aRestyleHint)) {
+ LOG_RESTYLE("reparenting style context");
+ newContext =
+ styleSet->ReparentStyleContext(oldContext, parentContext, element);
+ } else {
+ // Use ResolveStyleWithReplacement either for actual replacements
+ // or, with no replacements, as a substitute for
+ // ReparentStyleContext that rebuilds the path in the rule tree
+ // rather than reusing the rule node, as we need to do during a
+ // rule tree reconstruct.
+ Element* pseudoElement = PseudoElementForStyleContext(aSelf, pseudoType);
+ MOZ_ASSERT(!element || element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ LOG_RESTYLE("resolving style with replacement");
+ nsRestyleHint rshint = aRestyleHint & ~eRestyle_SomeDescendants;
+ newContext =
+ styleSet->ResolveStyleWithReplacement(element, pseudoElement,
+ parentContext, oldContext,
+ rshint);
+ }
+ } else if (pseudoType == CSSPseudoElementType::AnonBox) {
+ newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag,
+ parentContext);
+ }
+ else {
+ if (pseudoTag) {
+ if (pseudoTag == nsCSSPseudoElements::before ||
+ pseudoTag == nsCSSPseudoElements::after) {
+ // XXX what other pseudos do we need to treat like this?
+ newContext = styleSet->ProbePseudoElementStyle(element,
+ pseudoType,
+ parentContext,
+ mTreeMatchContext);
+ if (!newContext) {
+ // This pseudo should no longer exist; gotta reframe
+ mHintsHandled |= nsChangeHint_ReconstructFrame;
+ mChangeList->AppendChange(aSelf, element,
+ nsChangeHint_ReconstructFrame);
+ // We're reframing anyway; just keep the same context
+ newContext = oldContext;
+#ifdef DEBUG
+ // oldContext's parent might have had its style structs swapped out
+ // with parentContext, so to avoid any assertions that might
+ // otherwise trigger in oldContext's parent's destructor, we set a
+ // flag on oldContext to skip it and its descendants in
+ // nsStyleContext::AssertStructsNotUsedElsewhere.
+ if (oldContext->GetParent() != parentContext) {
+ oldContext->AddStyleBit(NS_STYLE_IS_GOING_AWAY);
+ }
+#endif
+ }
+ } else {
+ // Don't expect XUL tree stuff here, since it needs a comparator and
+ // all.
+ NS_ASSERTION(pseudoType < CSSPseudoElementType::Count,
+ "Unexpected pseudo type");
+ Element* pseudoElement =
+ PseudoElementForStyleContext(aSelf, pseudoType);
+ MOZ_ASSERT(element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ newContext = styleSet->ResolvePseudoElementStyle(element,
+ pseudoType,
+ parentContext,
+ pseudoElement);
+ }
+ }
+ else {
+ NS_ASSERTION(aSelf->GetContent(),
+ "non pseudo-element frame without content node");
+ // Skip parent display based style fixup for anonymous subtrees:
+ TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
+ parentDisplayBasedFixupSkipper(mTreeMatchContext,
+ element->IsRootOfNativeAnonymousSubtree());
+ newContext = styleSet->ResolveStyleFor(element, parentContext,
+ mTreeMatchContext);
+ }
+ }
+ }
+
+ MOZ_ASSERT(newContext);
+
+ if (!parentContext) {
+ if (oldContext->RuleNode() == newContext->RuleNode() &&
+ oldContext->IsLinkContext() == newContext->IsLinkContext() &&
+ oldContext->RelevantLinkVisited() ==
+ newContext->RelevantLinkVisited()) {
+ // We're the root of the style context tree and the new style
+ // context returned has the same rule node. This means that
+ // we can use FindChildWithRules to keep a lot of the old
+ // style contexts around. However, we need to start from the
+ // same root.
+ LOG_RESTYLE("restyling root and keeping old context");
+ LOG_RESTYLE_IF(this, result != RestyleResult::eContinue,
+ "continuing restyle since this is the root");
+ newContext = oldContext;
+ // Never consider stopping restyling at the root.
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+ }
+
+ LOG_RESTYLE("oldContext = %p, newContext = %p%s",
+ oldContext.get(), newContext.get(),
+ oldContext == newContext ? (const char*) " (same)" :
+ (const char*) "");
+
+ if (newContext != oldContext) {
+ if (oldContext->IsShared()) {
+ // If the old style context was shared, then we can't return
+ // RestyleResult::eStop and patch its parent to point to the
+ // new parent style context, as that change might not be valid
+ // for the other frames sharing the style context.
+ LOG_RESTYLE_CONTINUE("the old style context is shared");
+ result = RestyleResult::eContinue;
+
+ // It is not safe to return RestyleResult::eStopWithStyleChange
+ // when oldContext is shared and newContext has different
+ // inherited style data, regardless of whether the oldContext has
+ // that inherited style data cached. We can't simply rely on the
+ // samePointerStructs check later on, as the descendent style
+ // contexts just might not have had their inherited style data
+ // requested yet (which is possible for example if we flush style
+ // between resolving an initial style context for a frame and
+ // building its display list items). Therefore we must compare
+ // the rule nodes of oldContext and newContext to see if the
+ // restyle results in new inherited style data. If not, then
+ // we can continue assuming that RestyleResult::eStopWithStyleChange
+ // is safe. Without this check, we could end up with style contexts
+ // shared between elements which should have different styles.
+ if (!CommonInheritedStyleData(oldContext->RuleNode(),
+ newContext->RuleNode())) {
+ canStopWithStyleChange = false;
+ }
+ }
+
+ // Look at some details of the new style context to see if it would
+ // be safe to stop restyling, if we discover it has the same style
+ // data as the old style context.
+ ComputeRestyleResultFromNewContext(aSelf, newContext,
+ result, canStopWithStyleChange);
+
+ uint32_t equalStructs = 0;
+ uint32_t samePointerStructs = 0;
+
+ if (copyFromContinuation) {
+ // In theory we should know whether there was any style data difference,
+ // since we would have calculated that in the previous call to
+ // RestyleSelf, so until we perform only one restyling per chain-of-
+ // same-style continuations (bug 918064), we need to check again here to
+ // determine whether it is safe to stop restyling.
+ if (result == RestyleResult::eStop) {
+ oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ if (equalStructs != NS_STYLE_INHERIT_MASK) {
+ // At least one struct had different data in it, so we must
+ // continue restyling children.
+ LOG_RESTYLE_CONTINUE("there is different style data: %s",
+ RestyleManager::StructNamesToString(
+ ~equalStructs & NS_STYLE_INHERIT_MASK).get());
+ result = RestyleResult::eContinue;
+ }
+ }
+ } else {
+ bool changedStyle =
+ RestyleManager::TryInitiatingTransition(mPresContext,
+ aSelf->GetContent(),
+ oldContext, &newContext);
+ if (changedStyle) {
+ LOG_RESTYLE_CONTINUE("TryInitiatingTransition changed the new style "
+ "context");
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+ CaptureChange(oldContext, newContext, assumeDifferenceHint,
+ &equalStructs, &samePointerStructs);
+ if (equalStructs != NS_STYLE_INHERIT_MASK) {
+ // At least one struct had different data in it, so we must
+ // continue restyling children.
+ LOG_RESTYLE_CONTINUE("there is different style data: %s",
+ RestyleManager::StructNamesToString(
+ ~equalStructs & NS_STYLE_INHERIT_MASK).get());
+ result = RestyleResult::eContinue;
+ }
+ }
+
+ if (canStopWithStyleChange) {
+ // If any inherited struct pointers are different, or if any
+ // reset struct pointers are different and we have descendants
+ // that rely on those reset struct pointers, we can't return
+ // RestyleResult::eStopWithStyleChange.
+ if ((samePointerStructs & NS_STYLE_INHERITED_STRUCT_MASK) !=
+ NS_STYLE_INHERITED_STRUCT_MASK) {
+ LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
+ "there is different inherited data");
+ canStopWithStyleChange = false;
+ } else if ((samePointerStructs & NS_STYLE_RESET_STRUCT_MASK) !=
+ NS_STYLE_RESET_STRUCT_MASK &&
+ oldContext->HasChildThatUsesResetStyle()) {
+ LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
+ "there is different reset data and descendants use it");
+ canStopWithStyleChange = false;
+ }
+ }
+
+ if (result == RestyleResult::eStop) {
+ // Since we currently have RestyleResult::eStop, we know at this
+ // point that all of our style structs are equal in terms of styles.
+ // However, some of them might be different pointers. Since our
+ // descendants might share those pointers, we have to continue to
+ // restyling our descendants.
+ //
+ // However, because of the swapping of equal structs we've done on
+ // ancestors (later in this function), we've ensured that for structs
+ // that cannot be stored in the rule tree, we keep the old equal structs
+ // around rather than replacing them with new ones. This means that we
+ // only time we hit this deoptimization is either
+ //
+ // (a) when at least one of the (old or new) equal structs could be stored
+ // in the rule tree, and those structs are then inherited (by pointer
+ // sharing) to descendant style contexts; or
+ //
+ // (b) when we were unable to swap the structs on the parent because
+ // either or both of the old parent and new parent are shared.
+ //
+ // FIXME This loop could be rewritten as bit operations on
+ // oldContext->mBits and samePointerStructs.
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length;
+ sid = nsStyleStructID(sid + 1)) {
+ if (oldContext->HasCachedDependentStyleData(sid) &&
+ !(samePointerStructs & nsCachedStyleData::GetBitForSID(sid))) {
+ LOG_RESTYLE_CONTINUE("there are different struct pointers");
+ result = RestyleResult::eContinue;
+ break;
+ }
+ }
+ }
+
+ // From this point we no longer do any assignments of
+ // RestyleResult::eContinue to |result|. If canStopWithStyleChange is true,
+ // it means that we can convert |result| (whether it is
+ // RestyleResult::eContinue or RestyleResult::eStop) into
+ // RestyleResult::eStopWithStyleChange.
+ if (canStopWithStyleChange) {
+ LOG_RESTYLE("converting %s into RestyleResult::eStopWithStyleChange",
+ RestyleResultToString(result).get());
+ result = RestyleResult::eStopWithStyleChange;
+ }
+
+ if (aRestyleHint & eRestyle_ForceDescendants) {
+ result = RestyleResult::eContinueAndForceDescendants;
+ }
+
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ // If the frame gets regenerated, let it keep its old context,
+ // which is important to maintain various invariants about
+ // frame types matching their style contexts.
+ // Note that this check even makes sense if we didn't call
+ // CaptureChange because of copyFromContinuation being true,
+ // since we'll have copied the existing context from the
+ // previous continuation, so newContext == oldContext.
+
+ if (result != RestyleResult::eStop) {
+ if (copyFromContinuation) {
+ LOG_RESTYLE("not swapping style structs, since we copied from a "
+ "continuation");
+ } else if (oldContext->IsShared() && newContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since both old and contexts "
+ "are shared");
+ } else if (oldContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since the old context is "
+ "shared");
+ } else if (newContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since the new context is "
+ "shared");
+ } else {
+ if (result == RestyleResult::eStopWithStyleChange) {
+ LOG_RESTYLE("recording a style struct swap between %p and %p to "
+ "do if RestyleResult::eStopWithStyleChange fails",
+ oldContext.get(), newContext.get());
+ SwapInstruction* swap = aSwaps.AppendElement();
+ swap->mOldContext = oldContext;
+ swap->mNewContext = newContext;
+ swap->mStructsToSwap = equalStructs;
+ } else {
+ LOG_RESTYLE("swapping style structs between %p and %p",
+ oldContext.get(), newContext.get());
+ oldContext->SwapStyleData(newContext, equalStructs);
+ *aSwappedStructs |= equalStructs;
+ }
+#ifdef RESTYLE_LOGGING
+ uint32_t structs = RestyleManager::StructsToLog() & equalStructs;
+ if (structs) {
+ LOG_RESTYLE_INDENT();
+ LOG_RESTYLE("old style context now has: %s",
+ oldContext->GetCachedStyleDataAsString(structs).get());
+ LOG_RESTYLE("new style context now has: %s",
+ newContext->GetCachedStyleDataAsString(structs).get());
+ }
+#endif
+ }
+ LOG_RESTYLE("setting new style context");
+ aSelf->SetStyleContext(newContext);
+ }
+ } else {
+ LOG_RESTYLE("not setting new style context, since we'll reframe");
+ // We need to keep the new parent alive, in case it had structs
+ // swapped into it that our frame's style context still has cached.
+ // This is a similar scenario to the one described in the
+ // ElementRestyler::Restyle comment where we append to
+ // mSwappedStructOwners.
+ //
+ // We really only need to do this if we did swap structs on the
+ // parent, but we don't have that information here.
+ mSwappedStructOwners.AppendElement(newContext->GetParent());
+ }
+ } else {
+ if (aRestyleHint & eRestyle_ForceDescendants) {
+ result = RestyleResult::eContinueAndForceDescendants;
+ }
+ }
+ oldContext = nullptr;
+
+ // do additional contexts
+ // XXXbz might be able to avoid selector matching here in some
+ // cases; won't worry about it for now.
+ int32_t contextIndex = 0;
+ for (nsStyleContext* oldExtraContext;
+ (oldExtraContext = aSelf->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ LOG_RESTYLE("extra context %d", contextIndex);
+ LOG_RESTYLE_INDENT();
+ RefPtr<nsStyleContext> newExtraContext;
+ nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudo();
+ const CSSPseudoElementType extraPseudoType =
+ oldExtraContext->GetPseudoType();
+ NS_ASSERTION(extraPseudoTag &&
+ !nsCSSAnonBoxes::IsNonElement(extraPseudoTag),
+ "extra style context is not pseudo element");
+ Element* element = extraPseudoType != CSSPseudoElementType::AnonBox
+ ? mContent->AsElement() : nullptr;
+ if (!MustRestyleSelf(aRestyleHint, element)) {
+ if (CanReparentStyleContext(aRestyleHint)) {
+ newExtraContext =
+ styleSet->ReparentStyleContext(oldExtraContext, newContext, element);
+ } else {
+ // Use ResolveStyleWithReplacement as a substitute for
+ // ReparentStyleContext that rebuilds the path in the rule tree
+ // rather than reusing the rule node, as we need to do during a
+ // rule tree reconstruct.
+ Element* pseudoElement =
+ PseudoElementForStyleContext(aSelf, extraPseudoType);
+ MOZ_ASSERT(!element || element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ newExtraContext =
+ styleSet->ResolveStyleWithReplacement(element, pseudoElement,
+ newContext, oldExtraContext,
+ nsRestyleHint(0));
+ }
+ } else if (extraPseudoType == CSSPseudoElementType::AnonBox) {
+ newExtraContext = styleSet->ResolveAnonymousBoxStyle(extraPseudoTag,
+ newContext);
+ } else {
+ // Don't expect XUL tree stuff here, since it needs a comparator and
+ // all.
+ NS_ASSERTION(extraPseudoType < CSSPseudoElementType::Count,
+ "Unexpected type");
+ newExtraContext = styleSet->ResolvePseudoElementStyle(mContent->AsElement(),
+ extraPseudoType,
+ newContext,
+ nullptr);
+ }
+
+ MOZ_ASSERT(newExtraContext);
+
+ LOG_RESTYLE("newExtraContext = %p", newExtraContext.get());
+
+ if (oldExtraContext != newExtraContext) {
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ CaptureChange(oldExtraContext, newExtraContext, assumeDifferenceHint,
+ &equalStructs, &samePointerStructs);
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ LOG_RESTYLE("setting new extra style context");
+ aSelf->SetAdditionalStyleContext(contextIndex, newExtraContext);
+ } else {
+ LOG_RESTYLE("not setting new extra style context, since we'll reframe");
+ }
+ }
+ }
+
+ LOG_RESTYLE("returning %s", RestyleResultToString(result).get());
+
+ return result;
+}
+
+void
+ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint)
+{
+ MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame),
+ "No need to do this if we're planning to reframe already.");
+
+ // We'd like style resolution to be exact in the sense that an
+ // animation-only style flush flushes only the styles it requests
+ // flushing and doesn't update any other styles. This means avoiding
+ // constructing new frames during such a flush.
+ //
+ // For a ::before or ::after, we'll do an eRestyle_Subtree due to
+ // RestyleHintForOp in nsCSSRuleProcessor.cpp (via its
+ // HasAttributeDependentStyle or HasStateDependentStyle), given that
+ // we store pseudo-elements in selectors like they were children.
+ //
+ // Also, it's faster to skip the work we do on undisplayed children
+ // and pseudo-elements when we can skip it.
+ bool mightReframePseudos = aChildRestyleHint & eRestyle_Subtree;
+
+ RestyleUndisplayedDescendants(aChildRestyleHint);
+
+ // Check whether we might need to create a new ::before frame.
+ // There's no need to do this if we're planning to reframe already
+ // or if we're not forcing restyles on kids.
+ // It's also important to check mHintsHandled since we use
+ // mFrame->StyleContext(), which is out of date if mHintsHandled has a
+ // ReconstructFrame hint. Using an out of date style context could
+ // trigger assertions about mismatched rule trees.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+ mightReframePseudos) {
+ MaybeReframeForBeforePseudo();
+ }
+
+ // There is no need to waste time crawling into a frame's children
+ // on a frame change. The act of reconstructing frames will force
+ // new style contexts to be resolved on all of this frame's
+ // descendants anyway, so we want to avoid wasting time processing
+ // style contexts that we're just going to throw away anyway. - dwh
+ // It's also important to check mHintsHandled since reresolving the
+ // kids would use mFrame->StyleContext(), which is out of date if
+ // mHintsHandled has a ReconstructFrame hint; doing this could trigger
+ // assertions about mismatched rule trees.
+ nsIFrame* lastContinuation;
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+
+ for (nsIFrame* f = mFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ lastContinuation = f;
+ RestyleContentChildren(f, aChildRestyleHint);
+ }
+
+ SendAccessibilityNotifications();
+ }
+
+ // Check whether we might need to create a new ::after frame.
+ // See comments above regarding :before.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+ mightReframePseudos) {
+ MaybeReframeForAfterPseudo(lastContinuation);
+ }
+}
+
+void
+ElementRestyler::RestyleChildrenOfDisplayContentsElement(
+ nsIFrame* aParentFrame,
+ nsStyleContext* aNewContext,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame), "why call me?");
+
+ const bool mightReframePseudos = aRestyleHint & eRestyle_Subtree;
+ DoRestyleUndisplayedDescendants(nsRestyleHint(0), mContent, aNewContext);
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+ MaybeReframeForPseudo(CSSPseudoElementType::before,
+ aParentFrame, nullptr, mContent, aNewContext);
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+ MaybeReframeForPseudo(CSSPseudoElementType::after,
+ aParentFrame, nullptr, mContent, aNewContext);
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(aNewContext);
+
+ // Then process child frames for content that is a descendant of mContent.
+ // XXX perhaps it's better to walk child frames (before reresolving
+ // XXX undisplayed contexts above) and mark those that has a stylecontext
+ // XXX leading up to mContent's old context? (instead of the
+ // XXX ContentIsDescendantOf check below)
+ nsIFrame::ChildListIterator lists(aParentFrame);
+ for ( ; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* f : lists.CurrentList()) {
+ if (nsContentUtils::ContentIsDescendantOf(f->GetContent(), mContent) &&
+ !f->GetPrevContinuation()) {
+ if (!(f->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ ComputeStyleChangeFor(f, mChangeList, aMinHint, aRestyleTracker,
+ aRestyleHint, aRestyleHintData,
+ mContextsToClear, mSwappedStructOwners);
+ }
+ }
+ }
+ }
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ SendAccessibilityNotifications();
+ }
+}
+
+void
+ElementRestyler::ComputeStyleChangeFor(nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData,
+ nsTArray<ContextToClear>&
+ aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+{
+ nsIContent* content = aFrame->GetContent();
+ nsAutoCString localDescriptor;
+ if (profiler_is_active() && content) {
+ std::string elemDesc = ToString(*content);
+ localDescriptor.Assign(elemDesc.c_str());
+ }
+
+ PROFILER_LABEL_PRINTF("ElementRestyler", "ComputeStyleChangeFor",
+ js::ProfileEntry::Category::CSS,
+ content ? "Element: %s" : "%s",
+ content ? localDescriptor.get() : "");
+ if (aMinChange) {
+ aChangeList->AppendChange(aFrame, content, aMinChange);
+ }
+
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "must start with the first continuation");
+
+ // We want to start with this frame and walk all its next-in-flows,
+ // as well as all its ib-split siblings and their next-in-flows,
+ // reresolving style on all the frames we encounter in this walk that
+ // we didn't reach already. In the normal case, this will mean only
+ // restyling the first two block-in-inline splits and no
+ // continuations, and skipping everything else. However, when we have
+ // a style change targeted at an element inside a context where styles
+ // vary between continuations (e.g., a style change on an element that
+ // extends from inside a styled ::first-line to outside of that first
+ // line), we might restyle more than that.
+
+ nsPresContext* presContext = aFrame->PresContext();
+ FramePropertyTable* propTable = presContext->PropertyTable();
+
+ TreeMatchContext treeMatchContext(true,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ presContext->Document());
+ Element* parent =
+ content ? content->GetParentElementCrossingShadowRoot() : nullptr;
+ treeMatchContext.InitAncestors(parent);
+ nsTArray<nsCSSSelector*> selectorsForDescendants;
+ selectorsForDescendants.AppendElements(
+ aRestyleHintData.mSelectorsForDescendants);
+ nsTArray<nsIContent*> visibleKidsOfHiddenElement;
+ nsIFrame* nextIBSibling;
+ for (nsIFrame* ibSibling = aFrame; ibSibling; ibSibling = nextIBSibling) {
+ nextIBSibling = RestyleManager::GetNextBlockInInlineSibling(propTable, ibSibling);
+
+ if (nextIBSibling) {
+ // Don't allow some ib-split siblings to be processed with
+ // RestyleResult::eStopWithStyleChange and others not.
+ aRestyleHint |= eRestyle_Force;
+ }
+
+ // Outer loop over ib-split siblings
+ for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) {
+ if (GetPrevContinuationWithSameStyle(cont)) {
+ // We already handled this element when dealing with its earlier
+ // continuation.
+ continue;
+ }
+
+ // Inner loop over next-in-flows of the current frame
+ ElementRestyler restyler(presContext, cont, aChangeList,
+ aMinChange, aRestyleTracker,
+ selectorsForDescendants,
+ treeMatchContext,
+ visibleKidsOfHiddenElement,
+ aContextsToClear, aSwappedStructOwners);
+
+ restyler.Restyle(aRestyleHint);
+
+ if (restyler.HintsHandledForFrame() & nsChangeHint_ReconstructFrame) {
+ // If it's going to cause a framechange, then don't bother
+ // with the continuations or ib-split siblings since they'll be
+ // clobbered by the frame reconstruct anyway.
+ NS_ASSERTION(!cont->GetPrevContinuation(),
+ "continuing frame had more severe impact than first-in-flow");
+ return;
+ }
+ }
+ }
+}
+
+// The structure of this method parallels ConditionallyRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint)
+{
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(mFrame, undisplayedParent)) {
+ DoRestyleUndisplayedDescendants(aChildRestyleHint, undisplayedParent,
+ mFrame->StyleContext());
+ }
+}
+
+// The structure of this method parallels DoConditionallyRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::DoRestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint,
+ nsIContent* aParent,
+ nsStyleContext* aParentContext)
+{
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ UndisplayedNode* nodes = fc->GetAllUndisplayedContentIn(aParent);
+ RestyleUndisplayedNodes(aChildRestyleHint, nodes, aParent,
+ aParentContext, StyleDisplay::None);
+ nodes = fc->GetAllDisplayContentsIn(aParent);
+ RestyleUndisplayedNodes(aChildRestyleHint, nodes, aParent,
+ aParentContext, StyleDisplay::Contents);
+}
+
+// The structure of this method parallels ConditionallyRestyleUndisplayedNodes.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleUndisplayedNodes(nsRestyleHint aChildRestyleHint,
+ UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ nsStyleContext* aParentContext,
+ const StyleDisplay aDisplay)
+{
+ nsIContent* undisplayedParent = aUndisplayedParent;
+ UndisplayedNode* undisplayed = aUndisplayed;
+ TreeMatchContext::AutoAncestorPusher pusher(mTreeMatchContext);
+ if (undisplayed) {
+ pusher.PushAncestorAndStyleScope(undisplayedParent);
+ }
+ for (; undisplayed; undisplayed = undisplayed->mNext) {
+ NS_ASSERTION(undisplayedParent ||
+ undisplayed->mContent ==
+ mPresContext->Document()->GetRootElement(),
+ "undisplayed node child of null must be root");
+ NS_ASSERTION(!undisplayed->mStyle->GetPseudo(),
+ "Shouldn't have random pseudo style contexts in the "
+ "undisplayed map");
+
+ LOG_RESTYLE("RestyleUndisplayedChildren: undisplayed->mContent = %p",
+ undisplayed->mContent.get());
+
+ // Get the parent of the undisplayed content and check if it is a XBL
+ // children element. Push the children element as an ancestor here because it does
+ // not have a frame and would not otherwise be pushed as an ancestor.
+ nsIContent* parent = undisplayed->mContent->GetParent();
+ TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext);
+ if (parent && nsContentUtils::IsContentInsertionPoint(parent)) {
+ insertionPointPusher.PushAncestorAndStyleScope(parent);
+ }
+
+ nsRestyleHint thisChildHint = aChildRestyleHint;
+ nsAutoPtr<RestyleTracker::RestyleData> undisplayedRestyleData;
+ Element* element = undisplayed->mContent->AsElement();
+ if (mRestyleTracker.GetRestyleData(element,
+ undisplayedRestyleData)) {
+ thisChildHint =
+ nsRestyleHint(thisChildHint | undisplayedRestyleData->mRestyleHint);
+ }
+ RefPtr<nsStyleContext> undisplayedContext;
+ nsStyleSet* styleSet = StyleSet();
+ if (MustRestyleSelf(thisChildHint, element)) {
+ undisplayedContext =
+ styleSet->ResolveStyleFor(element, aParentContext, mTreeMatchContext);
+ } else if (CanReparentStyleContext(thisChildHint)) {
+ undisplayedContext =
+ styleSet->ReparentStyleContext(undisplayed->mStyle,
+ aParentContext,
+ element);
+ } else {
+ // Use ResolveStyleWithReplacement either for actual
+ // replacements, or as a substitute for ReparentStyleContext
+ // that rebuilds the path in the rule tree rather than reusing
+ // the rule node, as we need to do during a rule tree
+ // reconstruct.
+ nsRestyleHint rshint = thisChildHint & ~eRestyle_SomeDescendants;
+ undisplayedContext =
+ styleSet->ResolveStyleWithReplacement(element, nullptr,
+ aParentContext,
+ undisplayed->mStyle,
+ rshint);
+ }
+ const nsStyleDisplay* display = undisplayedContext->StyleDisplay();
+ if (display->mDisplay != aDisplay) {
+ NS_ASSERTION(element, "Must have undisplayed content");
+ mChangeList->AppendChange(nullptr, element,
+ nsChangeHint_ReconstructFrame);
+ // The node should be removed from the undisplayed map when
+ // we reframe it.
+ } else {
+ // update the undisplayed node with the new context
+ undisplayed->mStyle = undisplayedContext;
+
+ if (aDisplay == StyleDisplay::Contents) {
+ DoRestyleUndisplayedDescendants(aChildRestyleHint, element,
+ undisplayed->mStyle);
+ }
+ }
+ }
+}
+
+void
+ElementRestyler::MaybeReframeForBeforePseudo()
+{
+ MaybeReframeForPseudo(CSSPseudoElementType::before,
+ mFrame, mFrame, mFrame->GetContent(),
+ mFrame->StyleContext());
+}
+
+/**
+ * aFrame is the last continuation or block-in-inline sibling that this
+ * ElementRestyler is restyling.
+ */
+void
+ElementRestyler::MaybeReframeForAfterPseudo(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame);
+ MaybeReframeForPseudo(CSSPseudoElementType::after,
+ aFrame, aFrame, aFrame->GetContent(),
+ aFrame->StyleContext());
+}
+
+#ifdef DEBUG
+bool
+ElementRestyler::MustReframeForBeforePseudo()
+{
+ return MustReframeForPseudo(CSSPseudoElementType::before,
+ mFrame, mFrame, mFrame->GetContent(),
+ mFrame->StyleContext());
+}
+
+bool
+ElementRestyler::MustReframeForAfterPseudo(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame);
+ return MustReframeForPseudo(CSSPseudoElementType::after,
+ aFrame, aFrame, aFrame->GetContent(),
+ aFrame->StyleContext());
+}
+#endif
+
+void
+ElementRestyler::MaybeReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (MustReframeForPseudo(aPseudoType, aGenConParentFrame, aFrame, aContent,
+ aStyleContext)) {
+ // Have to create the new ::before/::after frame.
+ LOG_RESTYLE("MaybeReframeForPseudo, appending "
+ "nsChangeHint_ReconstructFrame");
+ mHintsHandled |= nsChangeHint_ReconstructFrame;
+ mChangeList->AppendChange(aFrame, aContent, nsChangeHint_ReconstructFrame);
+ }
+}
+
+bool
+ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ MOZ_ASSERT(aPseudoType == CSSPseudoElementType::before ||
+ aPseudoType == CSSPseudoElementType::after);
+
+ // Make sure not to do this for pseudo-frames...
+ if (aStyleContext->GetPseudo()) {
+ return false;
+ }
+
+ // ... or frames that can't have generated content.
+ if (!(aGenConParentFrame->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT)) {
+ // Our content insertion frame might have gotten flagged.
+ nsContainerFrame* cif = aGenConParentFrame->GetContentInsertionFrame();
+ if (!cif || !(cif->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT)) {
+ return false;
+ }
+ }
+
+ if (aPseudoType == CSSPseudoElementType::before) {
+ // Check for a ::before pseudo style and the absence of a ::before content,
+ // but only if aFrame is null or is the first continuation/ib-split.
+ if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
+ nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) {
+ return false;
+ }
+ } else {
+ // Similarly for ::after, but check for being the last continuation/
+ // ib-split.
+ if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
+ nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) {
+ return false;
+ }
+ }
+
+ // Checking for a ::before frame (which we do above) is cheaper than getting
+ // the ::before style context here.
+ return nsLayoutUtils::HasPseudoStyle(aContent, aStyleContext, aPseudoType,
+ mPresContext);
+}
+
+void
+ElementRestyler::InitializeAccessibilityNotifications(nsStyleContext* aNewContext)
+{
+#ifdef ACCESSIBILITY
+ // Notify a11y for primary frame only if it's a root frame of visibility
+ // changes or its parent frame was hidden while it stays visible and
+ // it is not inside a {ib} split or is the first frame of {ib} split.
+ if (nsIPresShell::IsAccessibilityActive() &&
+ (!mFrame ||
+ (!mFrame->GetPrevContinuation() &&
+ !mFrame->FrameIsNonFirstInIBSplit()))) {
+ if (mDesiredA11yNotifications == eSendAllNotifications) {
+ bool isFrameVisible = aNewContext->StyleVisibility()->IsVisible();
+ if (isFrameVisible != mWasFrameVisible) {
+ if (isFrameVisible) {
+ // Notify a11y the element (perhaps with its children) was shown.
+ // We don't fall into this case if this element gets or stays shown
+ // while its parent becomes hidden.
+ mKidsDesiredA11yNotifications = eSkipNotifications;
+ mOurA11yNotification = eNotifyShown;
+ } else {
+ // The element is being hidden; its children may stay visible, or
+ // become visible after being hidden previously. If we'll find
+ // visible children then we should notify a11y about that as if
+ // they were inserted into tree. Notify a11y this element was
+ // hidden.
+ mKidsDesiredA11yNotifications = eNotifyIfShown;
+ mOurA11yNotification = eNotifyHidden;
+ }
+ }
+ } else if (mDesiredA11yNotifications == eNotifyIfShown &&
+ aNewContext->StyleVisibility()->IsVisible()) {
+ // Notify a11y that element stayed visible while its parent was hidden.
+ nsIContent* c = mFrame ? mFrame->GetContent() : mContent;
+ mVisibleKidsOfHiddenElement.AppendElement(c);
+ mKidsDesiredA11yNotifications = eSkipNotifications;
+ }
+ }
+#endif
+}
+
+// The structure of this method parallels ConditionallyRestyleContentChildren.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleContentChildren(nsIFrame* aParent,
+ nsRestyleHint aChildRestyleHint)
+{
+ LOG_RESTYLE("RestyleContentChildren");
+
+ nsIFrame::ChildListIterator lists(aParent);
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(mTreeMatchContext);
+ if (!lists.IsDone()) {
+ ancestorPusher.PushAncestorAndStyleScope(mContent);
+ }
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Out-of-flows are reached through their placeholders. Continuations
+ // and block-in-inline splits are reached through those chains.
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ !GetPrevContinuationWithSameStyle(child)) {
+ // Get the parent of the child frame's content and check if it
+ // is a XBL children element. Push the children element as an
+ // ancestor here because it does not have a frame and would not
+ // otherwise be pushed as an ancestor.
+
+ // Check if the frame has a content because |child| may be a
+ // nsPageFrame that does not have a content.
+ nsIContent* parent = child->GetContent() ? child->GetContent()->GetParent() : nullptr;
+ TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext);
+ if (parent && nsContentUtils::IsContentInsertionPoint(parent)) {
+ insertionPointPusher.PushAncestorAndStyleScope(parent);
+ }
+
+ // only do frames that are in flow
+ if (nsGkAtoms::placeholderFrame == child->GetType()) { // placeholder
+ // get out of flow frame and recur there
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
+ NS_ASSERTION(outOfFlowFrame != mResolvedChild,
+ "out-of-flow frame not a true descendant");
+
+ // |nsFrame::GetParentStyleContext| checks being out
+ // of flow so that this works correctly.
+ do {
+ if (GetPrevContinuationWithSameStyle(outOfFlowFrame)) {
+ // Later continuations are likely restyled as a result of
+ // the restyling of the previous continuation.
+ // (Currently that's always true, but it's likely to
+ // change if we implement overflow:fragments or similar.)
+ continue;
+ }
+ ElementRestyler oofRestyler(*this, outOfFlowFrame,
+ FOR_OUT_OF_FLOW_CHILD);
+ oofRestyler.Restyle(aChildRestyleHint);
+ } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
+
+ // reresolve placeholder's context under the same parent
+ // as the out-of-flow frame
+ ElementRestyler phRestyler(*this, child, 0);
+ phRestyler.Restyle(aChildRestyleHint);
+ }
+ else { // regular child frame
+ if (child != mResolvedChild) {
+ ElementRestyler childRestyler(*this, child, 0);
+ childRestyler.Restyle(aChildRestyleHint);
+ }
+ }
+ }
+ }
+ }
+ // XXX need to do overflow frames???
+}
+
+void
+ElementRestyler::SendAccessibilityNotifications()
+{
+#ifdef ACCESSIBILITY
+ // Send notifications about visibility changes.
+ if (mOurA11yNotification == eNotifyShown) {
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIPresShell* presShell = mPresContext->GetPresShell();
+ nsIContent* content = mFrame ? mFrame->GetContent() : mContent;
+
+ accService->ContentRangeInserted(presShell, content->GetParent(),
+ content,
+ content->GetNextSibling());
+ }
+ } else if (mOurA11yNotification == eNotifyHidden) {
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIPresShell* presShell = mPresContext->GetPresShell();
+ nsIContent* content = mFrame ? mFrame->GetContent() : mContent;
+ accService->ContentRemoved(presShell, content);
+
+ // Process children staying shown.
+ uint32_t visibleContentCount = mVisibleKidsOfHiddenElement.Length();
+ for (uint32_t idx = 0; idx < visibleContentCount; idx++) {
+ nsIContent* childContent = mVisibleKidsOfHiddenElement[idx];
+ accService->ContentRangeInserted(presShell, childContent->GetParent(),
+ childContent,
+ childContent->GetNextSibling());
+ }
+ mVisibleKidsOfHiddenElement.Clear();
+ }
+ }
+#endif
+}
+
+static void
+ClearCachedInheritedStyleDataOnDescendants(
+ nsTArray<ElementRestyler::ContextToClear>& aContextsToClear)
+{
+ for (size_t i = 0; i < aContextsToClear.Length(); i++) {
+ auto& entry = aContextsToClear[i];
+ if (!entry.mStyleContext->HasSingleReference()) {
+ entry.mStyleContext->ClearCachedInheritedStyleDataOnDescendants(
+ entry.mStructs);
+ }
+ entry.mStyleContext = nullptr;
+ }
+}
+
+void
+RestyleManager::ComputeAndProcessStyleChange(nsIFrame* aFrame,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ nsStyleChangeList changeList;
+ nsTArray<ElementRestyler::ContextToClear> contextsToClear;
+
+ // swappedStructOwners needs to be kept alive until after
+ // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
+ // calls; see comment in ElementRestyler::Restyle.
+ nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
+ ElementRestyler::ComputeStyleChangeFor(aFrame, &changeList, aMinChange,
+ aRestyleTracker, aRestyleHint,
+ aRestyleHintData,
+ contextsToClear, swappedStructOwners);
+ ProcessRestyledFrames(changeList);
+ ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
+}
+
+void
+RestyleManager::ComputeAndProcessStyleChange(nsStyleContext* aNewContext,
+ Element* aElement,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ MOZ_ASSERT(aNewContext->StyleDisplay()->mDisplay == StyleDisplay::Contents);
+ nsIFrame* frame = GetNearestAncestorFrame(aElement);
+ MOZ_ASSERT(frame, "display:contents node in map although it's a "
+ "display:none descendant?");
+ TreeMatchContext treeMatchContext(true,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ frame->PresContext()->Document());
+ nsIContent* parent = aElement->GetParent();
+ Element* parentElement =
+ parent && parent->IsElement() ? parent->AsElement() : nullptr;
+ treeMatchContext.InitAncestors(parentElement);
+
+ nsTArray<nsCSSSelector*> selectorsForDescendants;
+ nsTArray<nsIContent*> visibleKidsOfHiddenElement;
+ nsTArray<ElementRestyler::ContextToClear> contextsToClear;
+
+ // swappedStructOwners needs to be kept alive until after
+ // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
+ // calls; see comment in ElementRestyler::Restyle.
+ nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
+ nsStyleChangeList changeList;
+ ElementRestyler r(frame->PresContext(), aElement, &changeList, aMinChange,
+ aRestyleTracker, selectorsForDescendants, treeMatchContext,
+ visibleKidsOfHiddenElement, contextsToClear,
+ swappedStructOwners);
+ r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
+ aRestyleTracker,
+ aRestyleHint, aRestyleHintData);
+ ProcessRestyledFrames(changeList);
+ ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
+}
+
+nsStyleSet*
+ElementRestyler::StyleSet() const
+{
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ElementRestyler should only be used with a Gecko-flavored "
+ "style backend");
+ return mPresContext->StyleSet()->AsGecko();
+}
+
+AutoDisplayContentsAncestorPusher::AutoDisplayContentsAncestorPusher(
+ TreeMatchContext& aTreeMatchContext, nsPresContext* aPresContext,
+ nsIContent* aParent)
+ : mTreeMatchContext(aTreeMatchContext)
+ , mPresContext(aPresContext)
+{
+ if (aParent) {
+ nsFrameManager* fm = mPresContext->FrameManager();
+ // Push display:contents mAncestors onto mTreeMatchContext.
+ for (nsIContent* p = aParent; p && fm->GetDisplayContentsStyleFor(p);
+ p = p->GetParent()) {
+ mAncestors.AppendElement(p->AsElement());
+ }
+ bool hasFilter = mTreeMatchContext.mAncestorFilter.HasFilter();
+ nsTArray<mozilla::dom::Element*>::size_type i = mAncestors.Length();
+ while (i--) {
+ if (hasFilter) {
+ mTreeMatchContext.mAncestorFilter.PushAncestor(mAncestors[i]);
+ }
+ mTreeMatchContext.PushStyleScope(mAncestors[i]);
+ }
+ }
+}
+
+AutoDisplayContentsAncestorPusher::~AutoDisplayContentsAncestorPusher()
+{
+ // Pop the ancestors we pushed in the CTOR, if any.
+ typedef nsTArray<mozilla::dom::Element*>::size_type sz;
+ sz len = mAncestors.Length();
+ bool hasFilter = mTreeMatchContext.mAncestorFilter.HasFilter();
+ for (sz i = 0; i < len; ++i) {
+ if (hasFilter) {
+ mTreeMatchContext.mAncestorFilter.PopAncestor();
+ }
+ mTreeMatchContext.PopStyleScope(mAncestors[i]);
+ }
+}
+
+#ifdef RESTYLE_LOGGING
+uint32_t
+RestyleManager::StructsToLog()
+{
+ static bool initialized = false;
+ static uint32_t structs;
+ if (!initialized) {
+ structs = 0;
+ const char* value = getenv("MOZ_DEBUG_RESTYLE_STRUCTS");
+ if (value) {
+ nsCString s(value);
+ while (!s.IsEmpty()) {
+ int32_t index = s.FindChar(',');
+ nsStyleStructID sid;
+ bool found;
+ if (index == -1) {
+ found = nsStyleContext::LookupStruct(s, sid);
+ s.Truncate();
+ } else {
+ found = nsStyleContext::LookupStruct(Substring(s, 0, index), sid);
+ s = Substring(s, index + 1);
+ }
+ if (found) {
+ structs |= nsCachedStyleData::GetBitForSID(sid);
+ }
+ }
+ }
+ initialized = true;
+ }
+ return structs;
+}
+#endif
+
+#ifdef DEBUG
+/* static */ nsCString
+RestyleManager::StructNamesToString(uint32_t aSIDs)
+{
+ nsCString result;
+ bool any = false;
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length;
+ sid = nsStyleStructID(sid + 1)) {
+ if (aSIDs & nsCachedStyleData::GetBitForSID(sid)) {
+ if (any) {
+ result.AppendLiteral(",");
+ }
+ result.AppendPrintf("%s", nsStyleContext::StructName(sid));
+ any = true;
+ }
+ }
+ return result;
+}
+
+/* static */ nsCString
+ElementRestyler::RestyleResultToString(RestyleResult aRestyleResult)
+{
+ nsCString result;
+ switch (aRestyleResult) {
+ case RestyleResult::eStop:
+ result.AssignLiteral("RestyleResult::eStop");
+ break;
+ case RestyleResult::eStopWithStyleChange:
+ result.AssignLiteral("RestyleResult::eStopWithStyleChange");
+ break;
+ case RestyleResult::eContinue:
+ result.AssignLiteral("RestyleResult::eContinue");
+ break;
+ case RestyleResult::eContinueAndForceDescendants:
+ result.AssignLiteral("RestyleResult::eContinueAndForceDescendants");
+ break;
+ default:
+ MOZ_ASSERT(aRestyleResult == RestyleResult::eNone,
+ "Unexpected RestyleResult");
+ }
+ return result;
+}
+#endif
+
+} // namespace mozilla
diff --git a/layout/base/RestyleManager.h b/layout/base/RestyleManager.h
new file mode 100644
index 000000000..e22fe9058
--- /dev/null
+++ b/layout/base/RestyleManager.h
@@ -0,0 +1,885 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Code responsible for managing style changes: tracking what style
+ * changes need to happen, scheduling them, and doing them.
+ */
+
+#ifndef mozilla_RestyleManager_h
+#define mozilla_RestyleManager_h
+
+#include "mozilla/RestyleLogging.h"
+#include "mozilla/RestyleManagerBase.h"
+#include "nsISupportsImpl.h"
+#include "nsChangeHint.h"
+#include "RestyleTracker.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTransitionManager.h"
+
+class nsIFrame;
+class nsStyleChangeList;
+struct TreeMatchContext;
+
+namespace mozilla {
+ enum class CSSPseudoElementType : uint8_t;
+ class EventStates;
+ struct UndisplayedNode;
+
+namespace dom {
+ class Element;
+} // namespace dom
+
+class RestyleManager final : public RestyleManagerBase
+{
+public:
+ typedef RestyleManagerBase base_type;
+
+ friend class RestyleTracker;
+ friend class ElementRestyler;
+
+ explicit RestyleManager(nsPresContext* aPresContext);
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~RestyleManager()
+ {
+ MOZ_ASSERT(!mReframingStyleContexts,
+ "temporary member should be nulled out before destruction");
+ MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
+ "leaving dangling pointers from AnimationsWithDestroyedFrame");
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(mozilla::RestyleManager)
+
+ // Forwarded nsIDocumentObserver method, to handle restyling (and
+ // passing the notification to the frame).
+ nsresult ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask);
+
+ // Forwarded nsIMutationObserver method, to handle restyling.
+ void AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue);
+ // Forwarded nsIMutationObserver method, to handle restyling (and
+ // passing the notification to the frame).
+ void AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue);
+
+ // Get a counter that increments on every style change, that we use to
+ // track whether off-main-thread animations are up-to-date.
+ uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
+
+ static uint64_t GetAnimationGenerationForFrame(nsIFrame* aFrame);
+
+ // Update the animation generation count to mark that animation state
+ // has changed.
+ //
+ // This is normally performed automatically by ProcessPendingRestyles
+ // but it is also called when we have out-of-band changes to animations
+ // such as changes made through the Web Animations API.
+ void IncrementAnimationGeneration() {
+ // We update the animation generation at start of each call to
+ // ProcessPendingRestyles so we should ignore any subsequent (redundant)
+ // calls that occur while we are still processing restyles.
+ if (!mIsProcessingRestyles) {
+ ++mAnimationGeneration;
+ }
+ }
+
+ // Whether rule matching should skip styles associated with animation
+ bool SkipAnimationRules() const { return mSkipAnimationRules; }
+
+ void SetSkipAnimationRules(bool aSkipAnimationRules) {
+ mSkipAnimationRules = aSkipAnimationRules;
+ }
+
+ /**
+ * Reparent the style contexts of this frame subtree. The parent frame of
+ * aFrame must be changed to the new parent before this function is called;
+ * the new parent style context will be automatically computed based on the
+ * new position in the frame tree.
+ *
+ * @param aFrame the root of the subtree to reparent. Must not be null.
+ */
+ nsresult ReparentStyleContext(nsIFrame* aFrame);
+
+ void ClearSelectors() {
+ mPendingRestyles.ClearSelectors();
+ }
+
+private:
+ // Used when restyling an element with a frame.
+ void ComputeAndProcessStyleChange(nsIFrame* aFrame,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData);
+
+ // Used when restyling a display:contents element.
+ void ComputeAndProcessStyleChange(nsStyleContext* aNewContext,
+ Element* aElement,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData);
+
+public:
+
+ /**
+ * In order to start CSS transitions on elements that are being
+ * reframed, we need to stash their style contexts somewhere during
+ * the reframing process.
+ *
+ * In all cases, the content node in the hash table is the real
+ * content node, not the anonymous content node we create for ::before
+ * or ::after. The content node passed to the Get and Put methods is,
+ * however, the content node to be associate with the frame's style
+ * context.
+ */
+ typedef nsRefPtrHashtable<nsRefPtrHashKey<nsIContent>, nsStyleContext>
+ ReframingStyleContextTable;
+ class MOZ_STACK_CLASS ReframingStyleContexts final {
+ public:
+ /**
+ * Construct a ReframingStyleContexts object. The caller must
+ * ensure that aRestyleManager lives at least as long as the
+ * object. (This is generally easy since the caller is typically a
+ * method of RestyleManager.)
+ */
+ explicit ReframingStyleContexts(RestyleManager* aRestyleManager);
+ ~ReframingStyleContexts();
+
+ void Put(nsIContent* aContent, nsStyleContext* aStyleContext) {
+ MOZ_ASSERT(aContent);
+ CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
+ if (pseudoType == CSSPseudoElementType::NotPseudo) {
+ mElementContexts.Put(aContent, aStyleContext);
+ } else if (pseudoType == CSSPseudoElementType::before) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
+ mBeforePseudoContexts.Put(aContent->GetParent(), aStyleContext);
+ } else if (pseudoType == CSSPseudoElementType::after) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
+ mAfterPseudoContexts.Put(aContent->GetParent(), aStyleContext);
+ }
+ }
+
+ nsStyleContext* Get(nsIContent* aContent,
+ CSSPseudoElementType aPseudoType) {
+ MOZ_ASSERT(aContent);
+ if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+ return mElementContexts.GetWeak(aContent);
+ }
+ if (aPseudoType == CSSPseudoElementType::before) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
+ return mBeforePseudoContexts.GetWeak(aContent->GetParent());
+ }
+ if (aPseudoType == CSSPseudoElementType::after) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
+ return mAfterPseudoContexts.GetWeak(aContent->GetParent());
+ }
+ MOZ_ASSERT(false, "unexpected aPseudoType");
+ return nullptr;
+ }
+ private:
+ RestyleManager* mRestyleManager;
+ AutoRestore<ReframingStyleContexts*> mRestorePointer;
+ ReframingStyleContextTable mElementContexts;
+ ReframingStyleContextTable mBeforePseudoContexts;
+ ReframingStyleContextTable mAfterPseudoContexts;
+ };
+
+ /**
+ * Return the current ReframingStyleContexts struct, or null if we're
+ * not currently in a restyling operation.
+ */
+ ReframingStyleContexts* GetReframingStyleContexts() {
+ return mReframingStyleContexts;
+ }
+
+ /**
+ * Try initiating a transition for an element or a ::before or ::after
+ * pseudo-element, given an old and new style context. This may
+ * change the new style context if a transition is started. Returns
+ * true if it does change aNewStyleContext.
+ *
+ * For the pseudo-elements, aContent must be the anonymous content
+ * that we're creating for that pseudo-element, not the real element.
+ */
+ static bool
+ TryInitiatingTransition(nsPresContext* aPresContext, nsIContent* aContent,
+ nsStyleContext* aOldStyleContext,
+ RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
+
+ // AnimationsWithDestroyedFrame is used to stop animations and transitions
+ // on elements that have no frame at the end of the restyling process.
+ // It only lives during the restyling process.
+ class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
+ public:
+ // Construct a AnimationsWithDestroyedFrame object. The caller must
+ // ensure that aRestyleManager lives at least as long as the
+ // object. (This is generally easy since the caller is typically a
+ // method of RestyleManager.)
+ explicit AnimationsWithDestroyedFrame(RestyleManager* aRestyleManager);
+
+ // This method takes the content node for the generated content for
+ // animation/transition on ::before and ::after, rather than the
+ // content node for the real element.
+ void Put(nsIContent* aContent, nsStyleContext* aStyleContext) {
+ MOZ_ASSERT(aContent);
+ CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
+ if (pseudoType == CSSPseudoElementType::NotPseudo) {
+ mContents.AppendElement(aContent);
+ } else if (pseudoType == CSSPseudoElementType::before) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
+ mBeforeContents.AppendElement(aContent->GetParent());
+ } else if (pseudoType == CSSPseudoElementType::after) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
+ mAfterContents.AppendElement(aContent->GetParent());
+ }
+ }
+
+ void StopAnimationsForElementsWithoutFrames();
+
+ private:
+ void StopAnimationsWithoutFrame(nsTArray<RefPtr<nsIContent>>& aArray,
+ CSSPseudoElementType aPseudoType);
+
+ RestyleManager* mRestyleManager;
+ AutoRestore<AnimationsWithDestroyedFrame*> mRestorePointer;
+
+ // Below three arrays might include elements that have already had their
+ // animations or transitions stopped.
+ //
+ // mBeforeContents and mAfterContents hold the real element rather than
+ // the content node for the generated content (which might change during
+ // a reframe)
+ nsTArray<RefPtr<nsIContent>> mContents;
+ nsTArray<RefPtr<nsIContent>> mBeforeContents;
+ nsTArray<RefPtr<nsIContent>> mAfterContents;
+ };
+
+ /**
+ * Return the current AnimationsWithDestroyedFrame struct, or null if we're
+ * not currently in a restyling operation.
+ */
+ AnimationsWithDestroyedFrame* GetAnimationsWithDestroyedFrame() {
+ return mAnimationsWithDestroyedFrame;
+ }
+
+private:
+ void RestyleForEmptyChange(Element* aContainer);
+
+public:
+ // Handle ContentInserted notifications.
+ void ContentInserted(nsINode* aContainer, nsIContent* aChild)
+ {
+ RestyleForInsertOrChange(aContainer, aChild);
+ }
+
+ // Handle ContentAppended notifications.
+ void ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent)
+ {
+ RestyleForAppend(aContainer, aFirstNewContent);
+ }
+
+ // Handle ContentRemoved notifications.
+ //
+ // This would be have the same logic as RestyleForInsertOrChange if we got the
+ // notification before the removal. However, we get it after, so we need the
+ // following sibling in addition to the old child. |aContainer| must be
+ // non-null; when the container is null, no work is needed. aFollowingSibling
+ // is the sibling that used to come after aOldChild before the removal.
+ void ContentRemoved(nsINode* aContainer, nsIContent* aOldChild,
+ nsIContent* aFollowingSibling);
+
+ // Restyling for a ContentInserted (notification after insertion) or
+ // for a CharacterDataChanged. |aContainer| must be non-null; when
+ // the container is null, no work is needed.
+ void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild);
+
+ // Restyling for a ContentAppended (notification after insertion) or
+ // for a CharacterDataChanged. |aContainer| must be non-null; when
+ // the container is null, no work is needed.
+ void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent);
+
+ // Process any pending restyles. This should be called after
+ // CreateNeededFrames.
+ // Note: It's the caller's responsibility to make sure to wrap a
+ // ProcessPendingRestyles call in a view update batch and a script blocker.
+ // This function does not call ProcessAttachedQueue() on the binding manager.
+ // If the caller wants that to happen synchronously, it needs to handle that
+ // itself.
+ void ProcessPendingRestyles();
+
+ // Returns whether there are any pending restyles.
+ bool HasPendingRestyles() { return mPendingRestyles.Count() != 0; }
+
+private:
+ // ProcessPendingRestyles calls into one of our RestyleTracker
+ // objects. It then calls back to these functions at the beginning
+ // and end of its work.
+ void BeginProcessingRestyles(RestyleTracker& aRestyleTracker);
+ void EndProcessingRestyles();
+
+public:
+ // Update styles for animations that are running on the compositor and
+ // whose updating is suppressed on the main thread (to save
+ // unnecessary work), while leaving all other aspects of style
+ // out-of-date.
+ //
+ // Performs an animation-only style flush to make styles from
+ // throttled transitions up-to-date prior to processing an unrelated
+ // style change, so that any transitions triggered by that style
+ // change produce correct results.
+ //
+ // In more detail: when we're able to run animations on the
+ // compositor, we sometimes "throttle" these animations by skipping
+ // updating style data on the main thread. However, whenever we
+ // process a normal (non-animation) style change, any changes in
+ // computed style on elements that have transition-* properties set
+ // may need to trigger new transitions; this process requires knowing
+ // both the old and new values of the property. To do this correctly,
+ // we need to have an up-to-date *old* value of the property on the
+ // primary frame. So the purpose of the mini-flush is to update the
+ // style for all throttled transitions and animations to the current
+ // animation state without making any other updates, so that when we
+ // process the queued style updates we'll have correct old data to
+ // compare against. When we do this, we don't bother touching frames
+ // other than primary frames.
+ void UpdateOnlyAnimationStyles();
+
+ // Rebuilds all style data by throwing out the old rule tree and
+ // building a new one, and additionally applying aExtraHint (which
+ // must not contain nsChangeHint_ReconstructFrame) to the root frame.
+ //
+ // aRestyleHint says which restyle hint to use for the computation;
+ // the only sensible values to use are eRestyle_Subtree (which says
+ // that the rebuild must run selector matching) and nsRestyleHint(0)
+ // (which says that rerunning selector matching is not required. (The
+ // method adds eRestyle_ForceDescendants internally, and including it
+ // in the restyle hint is harmless; some callers (e.g.,
+ // nsPresContext::MediaFeatureValuesChanged) might do this for their
+ // own reasons.)
+ void RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+
+ /**
+ * Notify the frame constructor that an element needs to have its
+ * style recomputed.
+ * @param aElement: The element to be restyled.
+ * @param aRestyleHint: Which nodes need to have selector matching run
+ * on them.
+ * @param aMinChangeHint: A minimum change hint for aContent and its
+ * descendants.
+ * @param aRestyleHintData: Additional data to go with aRestyleHint.
+ */
+ void PostRestyleEvent(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData = nullptr);
+
+ void PostRestyleEventForLazyConstruction()
+ {
+ PostRestyleEventInternal(true);
+ }
+
+public:
+ /**
+ * Asynchronously clear style data from the root frame downwards and ensure
+ * it will all be rebuilt. This is safe to call anytime; it will schedule
+ * a restyle and take effect next time style changes are flushed.
+ * This method is used to recompute the style data when some change happens
+ * outside of any style rules, like a color preference change or a change
+ * in a system font size, or to fix things up when an optimization in the
+ * style data has become invalid. We assume that the root frame will not
+ * need to be reframed.
+ *
+ * For parameters, see RebuildAllStyleData.
+ */
+ void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+
+#ifdef DEBUG
+ bool InRebuildAllStyleData() const { return mInRebuildAllStyleData; }
+#endif
+
+#ifdef RESTYLE_LOGGING
+ /**
+ * Returns whether a restyle event currently being processed by this
+ * RestyleManager should be logged.
+ */
+ bool ShouldLogRestyle() {
+ return ShouldLogRestyle(PresContext());
+ }
+
+ /**
+ * Returns whether a restyle event currently being processed for the
+ * document with the specified nsPresContext should be logged.
+ */
+ static bool ShouldLogRestyle(nsPresContext* aPresContext) {
+ return aPresContext->RestyleLoggingEnabled() &&
+ (!aPresContext->TransitionManager()->
+ InAnimationOnlyStyleUpdate() ||
+ AnimationRestyleLoggingEnabled());
+ }
+
+ static bool RestyleLoggingInitiallyEnabled() {
+ static bool enabled = getenv("MOZ_DEBUG_RESTYLE") != 0;
+ return enabled;
+ }
+
+ static bool AnimationRestyleLoggingEnabled() {
+ static bool animations = getenv("MOZ_DEBUG_RESTYLE_ANIMATIONS") != 0;
+ return animations;
+ }
+
+ // Set MOZ_DEBUG_RESTYLE_STRUCTS to a comma-separated string of
+ // style struct names -- such as "Font,SVGReset" -- to log the style context
+ // tree and those cached struct pointers before each restyle. This
+ // function returns a bitfield of the structs named in the
+ // environment variable.
+ static uint32_t StructsToLog();
+
+ static nsCString StructNamesToString(uint32_t aSIDs);
+ int32_t& LoggingDepth() { return mLoggingDepth; }
+#endif
+
+private:
+ inline nsStyleSet* StyleSet() const {
+ MOZ_ASSERT(PresContext()->StyleSet()->IsGecko(),
+ "RestyleManager should only be used with a Gecko-flavored "
+ "style backend");
+ return PresContext()->StyleSet()->AsGecko();
+ }
+
+ /* aMinHint is the minimal change that should be made to the element */
+ // XXXbz do we really need the aPrimaryFrame argument here?
+ void RestyleElement(Element* aElement,
+ nsIFrame* aPrimaryFrame,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData);
+
+ void StartRebuildAllStyleData(RestyleTracker& aRestyleTracker);
+ void FinishRebuildAllStyleData();
+
+ bool ShouldStartRebuildAllFor(RestyleTracker& aRestyleTracker) {
+ // When we process our primary restyle tracker and we have a pending
+ // rebuild-all, we need to process it.
+ return mDoRebuildAllStyleData &&
+ &aRestyleTracker == &mPendingRestyles;
+ }
+
+ void ProcessRestyles(RestyleTracker& aRestyleTracker) {
+ // Fast-path the common case (esp. for the animation restyle
+ // tracker) of not having anything to do.
+ if (aRestyleTracker.Count() || ShouldStartRebuildAllFor(aRestyleTracker)) {
+ IncrementRestyleGeneration();
+ aRestyleTracker.DoProcessRestyles();
+ }
+ }
+
+private:
+ // True if we need to reconstruct the rule tree the next time we
+ // process restyles.
+ bool mDoRebuildAllStyleData : 1;
+ // True if we're currently in the process of reconstructing the rule tree.
+ bool mInRebuildAllStyleData : 1;
+ // Whether rule matching should skip styles associated with animation
+ bool mSkipAnimationRules : 1;
+ bool mHavePendingNonAnimationRestyles : 1;
+
+ nsChangeHint mRebuildAllExtraHint;
+ nsRestyleHint mRebuildAllRestyleHint;
+
+ // The total number of animation flushes by this frame constructor.
+ // Used to keep the layer and animation manager in sync.
+ uint64_t mAnimationGeneration;
+
+ ReframingStyleContexts* mReframingStyleContexts;
+ AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame;
+
+ RestyleTracker mPendingRestyles;
+
+ // Are we currently in the middle of a call to ProcessRestyles?
+ // This flag is used both as a debugging aid to assert that we are not
+ // performing nested calls to ProcessPendingRestyles, as well as to ignore
+ // redundant calls to IncrementAnimationGeneration.
+ bool mIsProcessingRestyles;
+
+#ifdef RESTYLE_LOGGING
+ int32_t mLoggingDepth;
+#endif
+};
+
+/**
+ * An ElementRestyler is created for *each* element in a subtree that we
+ * recompute styles for.
+ */
+class ElementRestyler final
+{
+public:
+ typedef mozilla::dom::Element Element;
+
+ struct ContextToClear {
+ RefPtr<nsStyleContext> mStyleContext;
+ uint32_t mStructs;
+ };
+
+ // Construct for the root of the subtree that we're restyling.
+ ElementRestyler(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>& aSwappedStructOwners);
+
+ // Construct for an element whose parent is being restyled.
+ enum ConstructorFlags {
+ FOR_OUT_OF_FLOW_CHILD = 1<<0
+ };
+ ElementRestyler(const ElementRestyler& aParentRestyler,
+ nsIFrame* aFrame,
+ uint32_t aConstructorFlags);
+
+ // Construct for a frame whose parent is being restyled, but whose
+ // style context is the parent style context for its parent frame.
+ // (This is only used for table frames, whose style contexts are used
+ // as the parent style context for their table wrapper frame. We should
+ // probably try to get rid of this exception and have the inheritance go
+ // the other way.)
+ enum ParentContextFromChildFrame { PARENT_CONTEXT_FROM_CHILD_FRAME };
+ ElementRestyler(ParentContextFromChildFrame,
+ const ElementRestyler& aParentFrameRestyler,
+ nsIFrame* aFrame);
+
+ // For restyling undisplayed content only (mFrame==null).
+ ElementRestyler(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>& aSwappedStructOwners);
+
+ /**
+ * Restyle our frame's element and its subtree.
+ *
+ * Use eRestyle_Self for the aRestyleHint argument to mean
+ * "reresolve our style context but not kids", use eRestyle_Subtree
+ * to mean "reresolve our style context and kids", and use
+ * nsRestyleHint(0) to mean recompute a new style context for our
+ * current parent and existing rulenode, and the same for kids.
+ */
+ void Restyle(nsRestyleHint aRestyleHint);
+
+ /**
+ * mHintsHandled changes over time; it starts off as the hints that
+ * have been handled by ancestors, and by the end of Restyle it
+ * represents the hints that have been handled for this frame. This
+ * method is intended to be called after Restyle, to find out what
+ * hints have been handled for this frame.
+ */
+ nsChangeHint HintsHandledForFrame() { return mHintsHandled; }
+
+ /**
+ * Called from RestyleManager::ComputeAndProcessStyleChange to restyle
+ * children of a display:contents element.
+ */
+ void RestyleChildrenOfDisplayContentsElement(nsIFrame* aParentFrame,
+ nsStyleContext* aNewContext,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData&
+ aRestyleHintData);
+
+ /**
+ * Re-resolve the style contexts for a frame tree, building aChangeList
+ * based on the resulting style changes, plus aMinChange applied to aFrame.
+ */
+ static void ComputeStyleChangeFor(nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners);
+
+#ifdef RESTYLE_LOGGING
+ bool ShouldLogRestyle() {
+ return RestyleManager::ShouldLogRestyle(mPresContext);
+ }
+#endif
+
+private:
+ inline nsStyleSet* StyleSet() const;
+
+ // Enum class for the result of RestyleSelf, which indicates whether the
+ // restyle procedure should continue to the children, and how.
+ //
+ // These values must be ordered so that later values imply that all
+ // the work of the earlier values is also done.
+ enum class RestyleResult : uint8_t {
+ // default initial value
+ eNone,
+
+ // we left the old style context on the frame; do not restyle children
+ eStop,
+
+ // we got a new style context on this frame, but we know that children
+ // do not depend on the changed values; do not restyle children
+ eStopWithStyleChange,
+
+ // continue restyling children
+ eContinue,
+
+ // continue restyling children with eRestyle_ForceDescendants set
+ eContinueAndForceDescendants
+ };
+
+ struct SwapInstruction
+ {
+ RefPtr<nsStyleContext> mOldContext;
+ RefPtr<nsStyleContext> mNewContext;
+ uint32_t mStructsToSwap;
+ };
+
+ /**
+ * First half of Restyle().
+ */
+ RestyleResult RestyleSelf(nsIFrame* aSelf,
+ nsRestyleHint aRestyleHint,
+ uint32_t* aSwappedStructs,
+ nsTArray<SwapInstruction>& aSwaps);
+
+ /**
+ * Restyle the children of this frame (and, in turn, their children).
+ *
+ * Second half of Restyle().
+ */
+ void RestyleChildren(nsRestyleHint aChildRestyleHint);
+
+ /**
+ * Returns true iff a selector in mSelectorsForDescendants matches aElement.
+ * This is called when processing a eRestyle_SomeDescendants restyle hint.
+ */
+ bool SelectorMatchesForRestyle(Element* aElement);
+
+ /**
+ * Returns true iff aRestyleHint indicates that we should be restyling.
+ * Specifically, this will return true when eRestyle_Self or
+ * eRestyle_Subtree is present, or if eRestyle_SomeDescendants is
+ * present and the specified element matches one of the selectors in
+ * mSelectorsForDescendants.
+ */
+ bool MustRestyleSelf(nsRestyleHint aRestyleHint, Element* aElement);
+
+ /**
+ * Returns true iff aRestyleHint indicates that we can call
+ * ReparentStyleContext rather than any other restyling method of
+ * nsStyleSet that looks up a new rule node, and if we are
+ * not in the process of reconstructing the whole rule tree.
+ * This is used to check whether it is appropriate to call
+ * ReparentStyleContext.
+ */
+ bool CanReparentStyleContext(nsRestyleHint aRestyleHint);
+
+ /**
+ * Helpers for Restyle().
+ */
+ void AddLayerChangesForAnimation();
+
+ bool MoveStyleContextsForContentChildren(nsIFrame* aParent,
+ nsStyleContext* aOldContext,
+ nsTArray<nsStyleContext*>& aContextsToMove);
+ bool MoveStyleContextsForChildren(nsStyleContext* aOldContext);
+
+ /**
+ * Helpers for RestyleSelf().
+ */
+ void CaptureChange(nsStyleContext* aOldContext,
+ nsStyleContext* aNewContext,
+ nsChangeHint aChangeToAssume,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs);
+ void ComputeRestyleResultFromFrame(nsIFrame* aSelf,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange);
+ void ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
+ nsStyleContext* aNewContext,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange);
+
+ // Helpers for RestyleChildren().
+ void RestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint);
+ bool MustCheckUndisplayedContent(nsIFrame* aFrame,
+ nsIContent*& aUndisplayedParent);
+
+ /**
+ * In the following two methods, aParentStyleContext is either
+ * mFrame->StyleContext() if we have a frame, or a display:contents
+ * style context if we don't.
+ */
+ void DoRestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint,
+ nsIContent* aParent,
+ nsStyleContext* aParentStyleContext);
+ void RestyleUndisplayedNodes(nsRestyleHint aChildRestyleHint,
+ UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ nsStyleContext* aParentStyleContext,
+ const StyleDisplay aDisplay);
+ void MaybeReframeForBeforePseudo();
+ void MaybeReframeForAfterPseudo(nsIFrame* aFrame);
+ void MaybeReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+#ifdef DEBUG
+ bool MustReframeForBeforePseudo();
+ bool MustReframeForAfterPseudo(nsIFrame* aFrame);
+#endif
+ bool MustReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+ void RestyleContentChildren(nsIFrame* aParent,
+ nsRestyleHint aChildRestyleHint);
+ void InitializeAccessibilityNotifications(nsStyleContext* aNewContext);
+ void SendAccessibilityNotifications();
+
+ enum DesiredA11yNotifications {
+ eSkipNotifications,
+ eSendAllNotifications,
+ eNotifyIfShown
+ };
+
+ enum A11yNotificationType {
+ eDontNotify,
+ eNotifyShown,
+ eNotifyHidden
+ };
+
+ // These methods handle the eRestyle_SomeDescendants hint by traversing
+ // down the frame tree (and then when reaching undisplayed content,
+ // the flattened content tree) find elements that match a selector
+ // in mSelectorsForDescendants and call AddPendingRestyle for them.
+ void ConditionallyRestyleChildren();
+ void ConditionallyRestyleChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot);
+ void ConditionallyRestyleContentChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot);
+ void ConditionallyRestyleUndisplayedDescendants(nsIFrame* aFrame,
+ Element* aRestyleRoot);
+ void DoConditionallyRestyleUndisplayedDescendants(nsIContent* aParent,
+ Element* aRestyleRoot);
+ void ConditionallyRestyleUndisplayedNodes(UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ const StyleDisplay aDisplay,
+ Element* aRestyleRoot);
+ void ConditionallyRestyleContentDescendants(Element* aElement,
+ Element* aRestyleRoot);
+ bool ConditionallyRestyle(nsIFrame* aFrame, Element* aRestyleRoot);
+ bool ConditionallyRestyle(Element* aElement, Element* aRestyleRoot);
+
+#ifdef RESTYLE_LOGGING
+ int32_t& LoggingDepth() { return mLoggingDepth; }
+#endif
+
+#ifdef DEBUG
+ static nsCString RestyleResultToString(RestyleResult aRestyleResult);
+#endif
+
+private:
+ nsPresContext* const mPresContext;
+ nsIFrame* const mFrame;
+ nsIContent* const mParentContent;
+ // |mContent| is the node that we used for rule matching of
+ // normal elements (not pseudo-elements) and for which we generate
+ // framechange hints if we need them.
+ nsIContent* const mContent;
+ nsStyleChangeList* const mChangeList;
+ // We have already generated change list entries for hints listed in
+ // mHintsHandled (initially it's those handled by ancestors, but by
+ // the end of Restyle it is those handled for this frame as well). We
+ // need to generate a new change list entry for the frame when its
+ // style comparision returns a hint other than one of these hints.
+ nsChangeHint mHintsHandled;
+ // See nsStyleContext::CalcStyleDifference
+ nsChangeHint mParentFrameHintsNotHandledForDescendants;
+ nsChangeHint mHintsNotHandledForDescendants;
+ RestyleTracker& mRestyleTracker;
+ nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
+ TreeMatchContext& mTreeMatchContext;
+ nsIFrame* mResolvedChild; // child that provides our parent style context
+ // Array of style context subtrees in which we need to clear out cached
+ // structs at the end of the restyle (after change hints have been
+ // processed).
+ nsTArray<ContextToClear>& mContextsToClear;
+ // Style contexts that had old structs swapped into it and which should
+ // stay alive until the end of the restyle. (See comment in
+ // ElementRestyler::Restyle.)
+ nsTArray<RefPtr<nsStyleContext>>& mSwappedStructOwners;
+ // Whether this is the root of the restyle.
+ bool mIsRootOfRestyle;
+
+#ifdef ACCESSIBILITY
+ const DesiredA11yNotifications mDesiredA11yNotifications;
+ DesiredA11yNotifications mKidsDesiredA11yNotifications;
+ A11yNotificationType mOurA11yNotification;
+ nsTArray<nsIContent*>& mVisibleKidsOfHiddenElement;
+ bool mWasFrameVisible;
+#endif
+
+#ifdef RESTYLE_LOGGING
+ int32_t mLoggingDepth;
+#endif
+};
+
+/**
+ * This pushes any display:contents nodes onto a TreeMatchContext.
+ * Use it before resolving style for kids of aParent where aParent
+ * (and further ancestors) may be display:contents nodes which have
+ * not yet been pushed onto TreeMatchContext.
+ */
+class MOZ_RAII AutoDisplayContentsAncestorPusher final
+{
+ public:
+ typedef mozilla::dom::Element Element;
+ AutoDisplayContentsAncestorPusher(TreeMatchContext& aTreeMatchContext,
+ nsPresContext* aPresContext,
+ nsIContent* aParent);
+ ~AutoDisplayContentsAncestorPusher();
+ bool IsEmpty() const { return mAncestors.Length() == 0; }
+private:
+ TreeMatchContext& mTreeMatchContext;
+ nsPresContext* const mPresContext;
+ AutoTArray<mozilla::dom::Element*, 4> mAncestors;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_RestyleManager_h */
diff --git a/layout/base/RestyleManagerBase.cpp b/layout/base/RestyleManagerBase.cpp
new file mode 100644
index 000000000..9a5ce43eb
--- /dev/null
+++ b/layout/base/RestyleManagerBase.cpp
@@ -0,0 +1,1378 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/RestyleManagerBase.h"
+#include "mozilla/StyleSetHandle.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+RestyleManagerBase::RestyleManagerBase(nsPresContext* aPresContext)
+ : mPresContext(aPresContext)
+ , mRestyleGeneration(1)
+ , mHoverGeneration(0)
+ , mObservingRefreshDriver(false)
+ , mInStyleRefresh(false)
+{
+ MOZ_ASSERT(mPresContext);
+}
+
+/**
+ * Calculates the change hint and the restyle hint for a given content state
+ * change.
+ *
+ * This is called from both Restyle managers.
+ */
+void
+RestyleManagerBase::ContentStateChangedInternal(Element* aElement,
+ EventStates aStateMask,
+ nsChangeHint* aOutChangeHint,
+ nsRestyleHint* aOutRestyleHint)
+{
+ MOZ_ASSERT(aOutChangeHint);
+ MOZ_ASSERT(aOutRestyleHint);
+
+ StyleSetHandle styleSet = PresContext()->StyleSet();
+ NS_ASSERTION(styleSet, "couldn't get style set");
+
+ *aOutChangeHint = nsChangeHint(0);
+ // Any change to a content state that affects which frames we construct
+ // must lead to a frame reconstruct here if we already have a frame.
+ // Note that we never decide through non-CSS means to not create frames
+ // based on content states, so if we already don't have a frame we don't
+ // need to force a reframe -- if it's needed, the HasStateDependentStyle
+ // call will handle things.
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
+ if (primaryFrame) {
+ // If it's generated content, ignore LOADING/etc state changes on it.
+ if (!primaryFrame->IsGeneratedContentFrame() &&
+ aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |
+ NS_EVENT_STATE_USERDISABLED |
+ NS_EVENT_STATE_SUPPRESSED |
+ NS_EVENT_STATE_LOADING)) {
+ *aOutChangeHint = nsChangeHint_ReconstructFrame;
+ } else {
+ uint8_t app = primaryFrame->StyleDisplay()->mAppearance;
+ if (app) {
+ nsITheme* theme = PresContext()->GetTheme();
+ if (theme &&
+ theme->ThemeSupportsWidget(PresContext(), primaryFrame, app)) {
+ bool repaint = false;
+ theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint,
+ nullptr);
+ if (repaint) {
+ *aOutChangeHint |= nsChangeHint_RepaintFrame;
+ }
+ }
+ }
+ }
+
+ pseudoType = primaryFrame->StyleContext()->GetPseudoType();
+
+ primaryFrame->ContentStatesChanged(aStateMask);
+ }
+
+ if (pseudoType >= CSSPseudoElementType::Count) {
+ *aOutRestyleHint = styleSet->HasStateDependentStyle(aElement, aStateMask);
+ } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(
+ pseudoType)) {
+ // If aElement is a pseudo-element, we want to check to see whether there
+ // are any state-dependent rules applying to that pseudo.
+ Element* ancestor =
+ ElementForStyleContext(nullptr, primaryFrame, pseudoType);
+ *aOutRestyleHint = styleSet->HasStateDependentStyle(ancestor, pseudoType,
+ aElement, aStateMask);
+ } else {
+ *aOutRestyleHint = nsRestyleHint(0);
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && *aOutRestyleHint != 0) {
+ IncrementHoverGeneration();
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
+ // Exposing information to the page about whether the link is
+ // visited or not isn't really something we can worry about here.
+ // FIXME: We could probably do this a bit better.
+ *aOutChangeHint |= nsChangeHint_RepaintFrame;
+ }
+}
+
+/* static */ nsCString
+RestyleManagerBase::RestyleHintToString(nsRestyleHint aHint)
+{
+ nsCString result;
+ bool any = false;
+ const char* names[] = {
+ "Self", "SomeDescendants", "Subtree", "LaterSiblings", "CSSTransitions",
+ "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
+ "StyleAttribute_Animations", "Force", "ForceDescendants"
+ };
+ uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
+ uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
+ for (uint32_t i = 0; i < ArrayLength(names); i++) {
+ if (hint & (1 << i)) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("eRestyle_%s", names[i]);
+ any = true;
+ }
+ }
+ if (rest) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("0x%0x", rest);
+ } else {
+ if (!any) {
+ result.AppendLiteral("0");
+ }
+ }
+ return result;
+}
+
+#ifdef DEBUG
+/* static */ nsCString
+RestyleManagerBase::ChangeHintToString(nsChangeHint aHint)
+{
+ nsCString result;
+ bool any = false;
+ const char* names[] = {
+ "RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics",
+ "ClearDescendantIntrinsics", "NeedDirtyReflow", "SyncFrameView",
+ "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer",
+ "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow",
+ "UpdateSubtreeOverflow", "UpdatePostTransformOverflow",
+ "UpdateParentOverflow",
+ "ChildrenOnlyTransform", "RecomputePosition", "AddOrRemoveTransform",
+ "BorderStyleNoneChange", "UpdateTextPath", "SchedulePaint",
+ "NeutralChange", "InvalidateRenderingObservers",
+ "ReflowChangesSizeOrPosition", "UpdateComputedBSize",
+ "UpdateUsesOpacity", "UpdateBackgroundPosition",
+ "AddOrRemoveTransform"
+ };
+ static_assert(nsChangeHint_AllHints == (1 << ArrayLength(names)) - 1,
+ "Name list doesn't match change hints.");
+ uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
+ uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
+ if (hint == nsChangeHint_Hints_NotHandledForDescendants) {
+ result.AppendLiteral("nsChangeHint_Hints_NotHandledForDescendants");
+ hint = 0;
+ any = true;
+ } else {
+ if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
+ result.AppendLiteral("NS_STYLE_HINT_REFLOW");
+ hint = hint & ~NS_STYLE_HINT_REFLOW;
+ any = true;
+ } else if ((hint & nsChangeHint_AllReflowHints) == nsChangeHint_AllReflowHints) {
+ result.AppendLiteral("nsChangeHint_AllReflowHints");
+ hint = hint & ~nsChangeHint_AllReflowHints;
+ any = true;
+ } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
+ result.AppendLiteral("NS_STYLE_HINT_VISUAL");
+ hint = hint & ~NS_STYLE_HINT_VISUAL;
+ any = true;
+ }
+ }
+ for (uint32_t i = 0; i < ArrayLength(names); i++) {
+ if (hint & (1 << i)) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("nsChangeHint_%s", names[i]);
+ any = true;
+ }
+ }
+ if (rest) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("0x%0x", rest);
+ } else {
+ if (!any) {
+ result.AppendLiteral("nsChangeHint(0)");
+ }
+ }
+ return result;
+}
+#endif
+
+void
+RestyleManagerBase::PostRestyleEventInternal(bool aForLazyConstruction)
+{
+ // Make sure we're not in a style refresh; if we are, we still have
+ // a call to ProcessPendingRestyles coming and there's no need to
+ // add ourselves as a refresh observer until then.
+ bool inRefresh = !aForLazyConstruction && mInStyleRefresh;
+ nsIPresShell* presShell = PresContext()->PresShell();
+ if (!ObservingRefreshDriver() && !inRefresh) {
+ SetObservingRefreshDriver(PresContext()->RefreshDriver()->
+ AddStyleFlushObserver(presShell));
+ }
+
+ // Unconditionally flag our document as needing a flush. The other
+ // option here would be a dedicated boolean to track whether we need
+ // to do so (set here and unset in ProcessPendingRestyles).
+ presShell->GetDocument()->SetNeedStyleFlush();
+}
+
+/**
+ * Frame construction helpers follow.
+ */
+#ifdef DEBUG
+static bool gInApplyRenderingChangeToTree = false;
+#endif
+
+#ifdef DEBUG
+static void
+DumpContext(nsIFrame* aFrame, nsStyleContext* aContext)
+{
+ if (aFrame) {
+ fputs("frame: ", stdout);
+ nsAutoString name;
+ aFrame->GetFrameName(name);
+ fputs(NS_LossyConvertUTF16toASCII(name).get(), stdout);
+ fprintf(stdout, " (%p)", static_cast<void*>(aFrame));
+ }
+ if (aContext) {
+ fprintf(stdout, " style: %p ", static_cast<void*>(aContext));
+
+ nsIAtom* pseudoTag = aContext->GetPseudo();
+ if (pseudoTag) {
+ nsAutoString buffer;
+ pseudoTag->ToString(buffer);
+ fputs(NS_LossyConvertUTF16toASCII(buffer).get(), stdout);
+ fputs(" ", stdout);
+ }
+ fputs("{}\n", stdout);
+ }
+}
+
+static void
+VerifySameTree(nsStyleContext* aContext1, nsStyleContext* aContext2)
+{
+ nsStyleContext* top1 = aContext1;
+ nsStyleContext* top2 = aContext2;
+ nsStyleContext* parent;
+ for (;;) {
+ parent = top1->GetParent();
+ if (!parent)
+ break;
+ top1 = parent;
+ }
+ for (;;) {
+ parent = top2->GetParent();
+ if (!parent)
+ break;
+ top2 = parent;
+ }
+ NS_ASSERTION(top1 == top2,
+ "Style contexts are not in the same style context tree");
+}
+
+static void
+VerifyContextParent(nsIFrame* aFrame, nsStyleContext* aContext,
+ nsStyleContext* aParentContext)
+{
+ // get the contexts not provided
+ if (!aContext) {
+ aContext = aFrame->StyleContext();
+ }
+
+ if (!aParentContext) {
+ nsIFrame* providerFrame;
+ aParentContext = aFrame->GetParentStyleContext(&providerFrame);
+ // aParentContext could still be null
+ }
+
+ NS_ASSERTION(aContext, "Failure to get required contexts");
+ nsStyleContext* actualParentContext = aContext->GetParent();
+
+ if (aParentContext) {
+ if (aParentContext != actualParentContext) {
+ DumpContext(aFrame, aContext);
+ if (aContext == aParentContext) {
+ NS_ERROR("Using parent's style context");
+ } else {
+ NS_ERROR("Wrong parent style context");
+ fputs("Wrong parent style context: ", stdout);
+ DumpContext(nullptr, actualParentContext);
+ fputs("should be using: ", stdout);
+ DumpContext(nullptr, aParentContext);
+ VerifySameTree(actualParentContext, aParentContext);
+ fputs("\n", stdout);
+ }
+ }
+
+ } else {
+ if (actualParentContext) {
+ NS_ERROR("Have parent context and shouldn't");
+ DumpContext(aFrame, aContext);
+ fputs("Has parent context: ", stdout);
+ DumpContext(nullptr, actualParentContext);
+ fputs("Should be null\n\n", stdout);
+ }
+ }
+
+ nsStyleContext* childStyleIfVisited = aContext->GetStyleIfVisited();
+ // Either childStyleIfVisited has aContext->GetParent()->GetStyleIfVisited()
+ // as the parent or it has a different rulenode from aContext _and_ has
+ // aContext->GetParent() as the parent.
+ if (childStyleIfVisited &&
+ !((childStyleIfVisited->RuleNode() != aContext->RuleNode() &&
+ childStyleIfVisited->GetParent() == aContext->GetParent()) ||
+ childStyleIfVisited->GetParent() ==
+ aContext->GetParent()->GetStyleIfVisited())) {
+ NS_ERROR("Visited style has wrong parent");
+ DumpContext(aFrame, aContext);
+ fputs("\n", stdout);
+ }
+}
+
+static void
+VerifyStyleTree(nsIFrame* aFrame)
+{
+ nsStyleContext* context = aFrame->StyleContext();
+ VerifyContextParent(aFrame, context, nullptr);
+
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ // only do frames that are in flow
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ // placeholder: first recurse and verify the out of flow frame,
+ // then verify the placeholder's context
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+
+ // recurse to out of flow frame, letting the parent context get resolved
+ do {
+ VerifyStyleTree(outOfFlowFrame);
+ } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
+
+ // verify placeholder using the parent frame's context as
+ // parent context
+ VerifyContextParent(child, nullptr, nullptr);
+ } else { // regular frame
+ VerifyStyleTree(child);
+ }
+ }
+ }
+ }
+
+ // do additional contexts
+ int32_t contextIndex = 0;
+ for (nsStyleContext* extraContext;
+ (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ VerifyContextParent(aFrame, extraContext, context);
+ }
+}
+
+void
+RestyleManagerBase::DebugVerifyStyleTree(nsIFrame* aFrame)
+{
+ if (aFrame) {
+ VerifyStyleTree(aFrame);
+ }
+}
+
+#endif // DEBUG
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ChangeListProperty, bool)
+
+/**
+ * Sync views on aFrame and all of aFrame's descendants (following placeholders),
+ * if aChange has nsChangeHint_SyncFrameView.
+ * Calls DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants
+ * (following placeholders), if aChange has nsChangeHint_RepaintFrame.
+ * aFrame should be some combination of nsChangeHint_SyncFrameView,
+ * nsChangeHint_RepaintFrame, nsChangeHint_UpdateOpacityLayer and
+ * nsChangeHint_SchedulePaint, nothing else.
+*/
+static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
+ nsChangeHint aChange);
+
+static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
+
+/**
+ * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child
+ * frames of the SVG frame concerned. This helper function is used to find that
+ * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure
+ * that we iterate over the intended children, since sometimes we end up
+ * handling that hint while processing hints for one of the SVG frame's
+ * ancestor frames.
+ *
+ * The reason that we sometimes end up trying to process the hint for an
+ * ancestor of the SVG frame that the hint is intended for is due to the way we
+ * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
+ * the restyled element's principle frame to one of its ancestor frames based
+ * on what nsCSSRendering::FindBackground returns, since the background style
+ * may have been propagated up to an ancestor frame. Processing hints using an
+ * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
+ * a special case since it is intended to update the children of a specific
+ * frame.
+ */
+static nsIFrame*
+GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame)
+{
+ if (aFrame->GetType() == nsGkAtoms::viewportFrame) {
+ // This happens if the root-<svg> is fixed positioned, in which case we
+ // can't use aFrame->GetContent() to find the primary frame, since
+ // GetContent() returns nullptr for ViewportFrame.
+ aFrame = aFrame->PrincipalChildList().FirstChild();
+ }
+ // For an nsHTMLScrollFrame, this will get the SVG frame that has the
+ // children-only transforms:
+ aFrame = aFrame->GetContent()->GetPrimaryFrame();
+ if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
+ aFrame = aFrame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::svgOuterSVGAnonChildFrame,
+ "Where is the nsSVGOuterSVGFrame's anon child??");
+ }
+ MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer),
+ "Children-only transforms only expected on SVG frames");
+ return aFrame;
+}
+
+// Returns true if this function managed to successfully move a frame, and
+// false if it could not process the position change, and a reflow should
+// be performed instead.
+bool
+RecomputePosition(nsIFrame* aFrame)
+{
+ // Don't process position changes on table frames, since we already handle
+ // the dynamic position change on the table wrapper frame, and the
+ // reflow-based fallback code path also ignores positions on inner table
+ // frames.
+ if (aFrame->GetType() == nsGkAtoms::tableFrame) {
+ return true;
+ }
+
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ // Changes to the offsets of a non-positioned element can safely be ignored.
+ if (display->mPosition == NS_STYLE_POSITION_STATIC) {
+ return true;
+ }
+
+ // Don't process position changes on frames which have views or the ones which
+ // have a view somewhere in their descendants, because the corresponding view
+ // needs to be repositioned properly as well.
+ if (aFrame->HasView() ||
+ (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
+ return false;
+ }
+
+ aFrame->SchedulePaint();
+
+ // For relative positioning, we can simply update the frame rect
+ if (display->IsRelativelyPositionedStyle()) {
+ // Move the frame
+ if (display->mPosition == NS_STYLE_POSITION_STICKY) {
+ if (display->IsInnerTableStyle()) {
+ // We don't currently support sticky positioning of inner table
+ // elements (bug 975644). Bail.
+ //
+ // When this is fixed, remove the null-check for the computed
+ // offsets in nsTableRowFrame::ReflowChildren.
+ return true;
+ }
+
+ // Update sticky positioning for an entire element at once, starting with
+ // the first continuation or ib-split sibling.
+ // It's rare that the frame we already have isn't already the first
+ // continuation or ib-split sibling, but it can happen when styles differ
+ // across continuations such as ::first-line or ::first-letter, and in
+ // those cases we will generally (but maybe not always) do the work twice.
+ nsIFrame* firstContinuation =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
+ StickyScrollContainer* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(
+ firstContinuation);
+ if (ssc) {
+ ssc->PositionContinuations(firstContinuation);
+ }
+ } else {
+ MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
+ "Unexpected type of positioning");
+ for (nsIFrame* cont = aFrame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ nsIFrame* cb = cont->GetContainingBlock();
+ nsMargin newOffsets;
+ WritingMode wm = cb->GetWritingMode();
+ const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size());
+
+ ReflowInput::ComputeRelativeOffsets(wm, cont, size, newOffsets);
+ NS_ASSERTION(newOffsets.left == -newOffsets.right &&
+ newOffsets.top == -newOffsets.bottom,
+ "ComputeRelativeOffsets should return valid results");
+
+ // ReflowInput::ApplyRelativePositioning would work here, but
+ // since we've already checked mPosition and aren't changing the frame's
+ // normal position, go ahead and add the offsets directly.
+ // First, we need to ensure that the normal position is stored though.
+ nsPoint normalPosition = cont->GetNormalPosition();
+ auto props = cont->Properties();
+ const auto& prop = nsIFrame::NormalPositionProperty();
+ if (!props.Get(prop)) {
+ props.Set(prop, new nsPoint(normalPosition));
+ }
+ cont->SetPosition(normalPosition +
+ nsPoint(newOffsets.left, newOffsets.top));
+ }
+ }
+
+ return true;
+ }
+
+ // For the absolute positioning case, set up a fake HTML reflow state for
+ // the frame, and then get the offsets and size from it. If the frame's size
+ // doesn't need to change, we can simply update the frame position. Otherwise
+ // we fall back to a reflow.
+ nsRenderingContext rc(
+ aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext());
+
+ // Construct a bogus parent reflow state so that there's a usable
+ // containing block reflow state.
+ nsIFrame* parentFrame = aFrame->GetParent();
+ WritingMode parentWM = parentFrame->GetWritingMode();
+ WritingMode frameWM = aFrame->GetWritingMode();
+ LogicalSize parentSize = parentFrame->GetLogicalSize();
+
+ nsFrameState savedState = parentFrame->GetStateBits();
+ ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, &rc,
+ parentSize);
+ parentFrame->RemoveStateBits(~nsFrameState(0));
+ parentFrame->AddStateBits(savedState);
+
+ // The bogus parent state here was created with no parent state of its own,
+ // and therefore it won't have an mCBReflowInput set up.
+ // But we may need one (for InitCBReflowInput in a child state), so let's
+ // try to create one here for the cases where it will be needed.
+ Maybe<ReflowInput> cbReflowInput;
+ nsIFrame* cbFrame = parentFrame->GetContainingBlock();
+ if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
+ parentFrame->GetType() == nsGkAtoms::tableFrame)) {
+ LogicalSize cbSize = cbFrame->GetLogicalSize();
+ cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, &rc, cbSize);
+ cbReflowInput->ComputedPhysicalMargin() = cbFrame->GetUsedMargin();
+ cbReflowInput->ComputedPhysicalPadding() = cbFrame->GetUsedPadding();
+ cbReflowInput->ComputedPhysicalBorderPadding() =
+ cbFrame->GetUsedBorderAndPadding();
+ parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
+ }
+
+ NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_INTRINSICSIZE &&
+ parentSize.BSize(parentWM) != NS_INTRINSICSIZE,
+ "parentSize should be valid");
+ parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
+ parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
+ parentReflowInput.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0);
+
+ parentReflowInput.ComputedPhysicalPadding() = parentFrame->GetUsedPadding();
+ parentReflowInput.ComputedPhysicalBorderPadding() =
+ parentFrame->GetUsedBorderAndPadding();
+ LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
+ availSize.BSize(frameWM) = NS_INTRINSICSIZE;
+
+ ViewportFrame* viewport = do_QueryFrame(parentFrame);
+ nsSize cbSize = viewport ?
+ viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput).Size()
+ : aFrame->GetContainingBlock()->GetSize();
+ const nsMargin& parentBorder =
+ parentReflowInput.mStyleBorder->GetComputedBorder();
+ cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
+ LogicalSize lcbSize(frameWM, cbSize);
+ ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
+ availSize, &lcbSize);
+ nsSize computedSize(reflowInput.ComputedWidth(),
+ reflowInput.ComputedHeight());
+ computedSize.width += reflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ if (computedSize.height != NS_INTRINSICSIZE) {
+ computedSize.height +=
+ reflowInput.ComputedPhysicalBorderPadding().TopBottom();
+ }
+ nsSize size = aFrame->GetSize();
+ // The RecomputePosition hint is not used if any offset changed between auto
+ // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new
+ // element height will be its intrinsic height, and since 'top' and 'bottom''s
+ // auto-ness hasn't changed, the old height must also be its intrinsic
+ // height, which we can assume hasn't changed (or reflow would have
+ // been triggered).
+ if (computedSize.width == size.width &&
+ (computedSize.height == NS_INTRINSICSIZE || computedSize.height == size.height)) {
+ // If we're solving for 'left' or 'top', then compute it here, in order to
+ // match the reflow code path.
+ if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().left) {
+ reflowInput.ComputedPhysicalOffsets().left = cbSize.width -
+ reflowInput.ComputedPhysicalOffsets().right -
+ reflowInput.ComputedPhysicalMargin().right -
+ size.width -
+ reflowInput.ComputedPhysicalMargin().left;
+ }
+
+ if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().top) {
+ reflowInput.ComputedPhysicalOffsets().top = cbSize.height -
+ reflowInput.ComputedPhysicalOffsets().bottom -
+ reflowInput.ComputedPhysicalMargin().bottom -
+ size.height -
+ reflowInput.ComputedPhysicalMargin().top;
+ }
+
+ // Move the frame
+ nsPoint pos(parentBorder.left + reflowInput.ComputedPhysicalOffsets().left +
+ reflowInput.ComputedPhysicalMargin().left,
+ parentBorder.top + reflowInput.ComputedPhysicalOffsets().top +
+ reflowInput.ComputedPhysicalMargin().top);
+ aFrame->SetPosition(pos);
+
+ return true;
+ }
+
+ // Fall back to a reflow
+ StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
+ return false;
+}
+
+static bool
+HasBoxAncestor(nsIFrame* aFrame)
+{
+ for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ if (f->IsXULBoxFrame()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return true if aFrame's subtree has placeholders for out-of-flow content
+ * whose 'position' style's bit in aPositionMask is set.
+ */
+static bool
+FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame,
+ uint32_t aPositionMask)
+{
+ MOZ_ASSERT(aPositionMask & (1 << NS_STYLE_POSITION_FIXED));
+
+ for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* f : lists.CurrentList()) {
+ if (f->GetType() == nsGkAtoms::placeholderFrame) {
+ nsIFrame* outOfFlow =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ // If SVG text frames could appear here, they could confuse us since
+ // they ignore their position style ... but they can't.
+ NS_ASSERTION(!outOfFlow->IsSVGText(),
+ "SVG text frames can't be out of flow");
+ if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) {
+ return true;
+ }
+ }
+ uint32_t positionMask = aPositionMask;
+ // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or
+ // f->IsFixedPosContainingBlock() here. However, that would only
+ // be testing the *new* style of the frame, which might exclude
+ // descendants that currently have this frame as an abs-pos
+ // containing block. Taking the codepath where we don't reframe
+ // could lead to an unsafe call to
+ // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
+ // the descendant and taken it off the absolute list.
+ if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool
+NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame)
+{
+ static_assert(0 <= NS_STYLE_POSITION_ABSOLUTE &&
+ NS_STYLE_POSITION_ABSOLUTE < 32, "Style constant out of range");
+ static_assert(0 <= NS_STYLE_POSITION_FIXED &&
+ NS_STYLE_POSITION_FIXED < 32, "Style constant out of range");
+
+ uint32_t positionMask;
+ // Don't call aFrame->IsPositioned here, since that returns true if
+ // the frame already has a transform, and we want to ignore that here
+ if (aFrame->IsAbsolutelyPositioned() || aFrame->IsRelativelyPositioned()) {
+ // This frame is a container for abs-pos descendants whether or not it
+ // has a transform.
+ // So abs-pos descendants are no problem; we only need to reframe if
+ // we have fixed-pos descendants.
+ positionMask = 1 << NS_STYLE_POSITION_FIXED;
+ } else {
+ // This frame may not be a container for abs-pos descendants already.
+ // So reframe if we have abs-pos or fixed-pos descendants.
+ positionMask =
+ (1 << NS_STYLE_POSITION_FIXED) | (1 << NS_STYLE_POSITION_ABSOLUTE);
+ }
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ nsIFrame*
+RestyleManagerBase::GetNearestAncestorFrame(nsIContent* aContent)
+{
+ nsIFrame* ancestorFrame = nullptr;
+ for (nsIContent* ancestor = aContent->GetParent();
+ ancestor && !ancestorFrame;
+ ancestor = ancestor->GetParent()) {
+ ancestorFrame = ancestor->GetPrimaryFrame();
+ }
+ return ancestorFrame;
+}
+
+/* static */ nsIFrame*
+RestyleManagerBase::GetNextBlockInInlineSibling(FramePropertyTable* aPropTable,
+ nsIFrame* aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "must start with the first continuation");
+ // Might we have ib-split siblings?
+ if (!(aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // nothing more to do here
+ return nullptr;
+ }
+
+ return static_cast<nsIFrame*>
+ (aPropTable->Get(aFrame, nsIFrame::IBSplitSibling()));
+}
+
+static void
+DoApplyRenderingChangeToTree(nsIFrame* aFrame,
+ nsChangeHint aChange)
+{
+ NS_PRECONDITION(gInApplyRenderingChangeToTree,
+ "should only be called within ApplyRenderingChangeToTree");
+
+ for ( ; aFrame; aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
+ // Invalidate and sync views on all descendant frames, following placeholders.
+ // We don't need to update transforms in SyncViewsAndInvalidateDescendants, because
+ // there can't be any out-of-flows or popups that need to be transformed;
+ // all out-of-flow descendants of the transformed element must also be
+ // descendants of the transformed frame.
+ SyncViewsAndInvalidateDescendants(aFrame,
+ nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
+ nsChangeHint_SyncFrameView |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_SchedulePaint)));
+ // This must be set to true if the rendering change needs to
+ // invalidate content. If it's false, a composite-only paint
+ // (empty transaction) will be scheduled.
+ bool needInvalidatingPaint = false;
+
+ // if frame has view, will already be invalidated
+ if (aChange & nsChangeHint_RepaintFrame) {
+ // Note that this whole block will be skipped when painting is suppressed
+ // (due to our caller ApplyRendingChangeToTree() discarding the
+ // nsChangeHint_RepaintFrame hint). If you add handling for any other
+ // hints within this block, be sure that they too should be ignored when
+ // painting is suppressed.
+ needInvalidatingPaint = true;
+ aFrame->InvalidateFrameSubtree();
+ if ((aChange & nsChangeHint_UpdateEffects) &&
+ aFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
+ // Need to update our overflow rects:
+ nsSVGUtils::ScheduleReflowSVG(aFrame);
+ }
+ }
+ if (aChange & nsChangeHint_UpdateTextPath) {
+ if (aFrame->IsSVGText()) {
+ // Invalidate and reflow the entire SVGTextFrame:
+ NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
+ "expected frame for a <textPath> element");
+ nsIFrame* text =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::svgTextFrame);
+ NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame");
+ static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange();
+ } else {
+ MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath");
+ }
+ }
+ if (aChange & nsChangeHint_UpdateOpacityLayer) {
+ // FIXME/bug 796697: we can get away with empty transactions for
+ // opacity updates in many cases.
+ needInvalidatingPaint = true;
+
+ ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
+ if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
+ // SVG effects paints the opacity without using
+ // nsDisplayOpacity. We need to invalidate manually.
+ aFrame->InvalidateFrameSubtree();
+ }
+ }
+ if ((aChange & nsChangeHint_UpdateTransformLayer) &&
+ aFrame->IsTransformed()) {
+ ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
+ // If we're not already going to do an invalidating paint, see
+ // if we can get away with only updating the transform on a
+ // layer for this frame, and not scheduling an invalidating
+ // paint.
+ if (!needInvalidatingPaint) {
+ Layer* layer;
+ needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer);
+
+ if (!needInvalidatingPaint) {
+ // Since we're not going to paint, we need to resend animation
+ // data to the layer.
+ MOZ_ASSERT(layer, "this can't happen if there's no layer");
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
+ layer, nullptr, nullptr, aFrame, eCSSProperty_transform);
+ }
+ }
+ }
+ if (aChange & nsChangeHint_ChildrenOnlyTransform) {
+ needInvalidatingPaint = true;
+ nsIFrame* childFrame =
+ GetFrameForChildrenOnlyTransformHint(aFrame)->PrincipalChildList().FirstChild();
+ for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
+ ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
+ }
+ }
+ if (aChange & nsChangeHint_SchedulePaint) {
+ needInvalidatingPaint = true;
+ }
+ aFrame->SchedulePaint(needInvalidatingPaint
+ ? nsIFrame::PAINT_DEFAULT
+ : nsIFrame::PAINT_COMPOSITE_ONLY);
+ }
+}
+
+static void
+SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, nsChangeHint aChange)
+{
+ NS_PRECONDITION(gInApplyRenderingChangeToTree,
+ "should only be called within ApplyRenderingChangeToTree");
+ NS_ASSERTION(nsChangeHint_size_t(aChange) ==
+ (aChange & (nsChangeHint_RepaintFrame |
+ nsChangeHint_SyncFrameView |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_SchedulePaint)),
+ "Invalid change flag");
+
+ nsView* view = aFrame->GetView();
+ if (view) {
+ if (aChange & nsChangeHint_SyncFrameView) {
+ nsContainerFrame::SyncFrameViewProperties(aFrame->PresContext(), aFrame,
+ nullptr, view);
+ }
+ }
+
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ // only do frames that don't have placeholders
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ // do the out-of-flow frame and its continuations
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
+ } else if (lists.CurrentID() == nsIFrame::kPopupList) {
+ DoApplyRenderingChangeToTree(child, aChange);
+ } else { // regular frame
+ SyncViewsAndInvalidateDescendants(child, aChange);
+ }
+ }
+ }
+ }
+}
+
+static void
+ApplyRenderingChangeToTree(nsIPresShell* aPresShell,
+ nsIFrame* aFrame,
+ nsChangeHint aChange)
+{
+ // We check StyleDisplay()->HasTransformStyle() in addition to checking
+ // IsTransformed() since we can get here for some frames that don't support
+ // CSS transforms.
+ NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
+ aFrame->IsTransformed() ||
+ aFrame->StyleDisplay()->HasTransformStyle(),
+ "Unexpected UpdateTransformLayer hint");
+
+ if (aPresShell->IsPaintingSuppressed()) {
+ // Don't allow synchronous rendering changes when painting is turned off.
+ aChange &= ~nsChangeHint_RepaintFrame;
+ if (!aChange) {
+ return;
+ }
+ }
+
+// Trigger rendering updates by damaging this frame and any
+// continuations of this frame.
+#ifdef DEBUG
+ gInApplyRenderingChangeToTree = true;
+#endif
+ if (aChange & nsChangeHint_RepaintFrame) {
+ // If the frame's background is propagated to an ancestor, walk up to
+ // that ancestor and apply the RepaintFrame change hint to it.
+ nsStyleContext* bgSC;
+ nsIFrame* propagatedFrame = aFrame;
+ while (!nsCSSRendering::FindBackground(propagatedFrame, &bgSC)) {
+ propagatedFrame = propagatedFrame->GetParent();
+ NS_ASSERTION(aFrame, "root frame must paint");
+ }
+
+ if (propagatedFrame != aFrame) {
+ DoApplyRenderingChangeToTree(propagatedFrame, nsChangeHint_RepaintFrame);
+ aChange &= ~nsChangeHint_RepaintFrame;
+ if (!aChange) {
+ return;
+ }
+ }
+ }
+ DoApplyRenderingChangeToTree(aFrame, aChange);
+#ifdef DEBUG
+ gInApplyRenderingChangeToTree = false;
+#endif
+}
+
+static void
+AddSubtreeToOverflowTracker(nsIFrame* aFrame,
+ OverflowChangedTracker& aOverflowChangedTracker)
+{
+ if (aFrame->FrameMaintainsOverflow()) {
+ aOverflowChangedTracker.AddFrame(aFrame,
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
+ }
+ }
+}
+
+static void
+StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint)
+{
+ nsIPresShell::IntrinsicDirty dirtyType;
+ if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
+ NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
+ "Please read the comments in nsChangeHint.h");
+ NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
+ "ClearDescendantIntrinsics requires NeedDirtyReflow");
+ dirtyType = nsIPresShell::eStyleChange;
+ } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
+ aFrame->HasAnyStateBits(
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ dirtyType = nsIPresShell::eStyleChange;
+ } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
+ dirtyType = nsIPresShell::eTreeChange;
+ } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
+ HasBoxAncestor(aFrame)) {
+ // The frame's computed BSize is changing, and we have a box ancestor
+ // whose cached intrinsic height may need to be updated.
+ dirtyType = nsIPresShell::eTreeChange;
+ } else {
+ dirtyType = nsIPresShell::eResize;
+ }
+
+ nsFrameState dirtyBits;
+ if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
+ dirtyBits = nsFrameState(0);
+ } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
+ dirtyType == nsIPresShell::eStyleChange) {
+ dirtyBits = NS_FRAME_IS_DIRTY;
+ } else {
+ dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
+ }
+
+ // If we're not going to clear any intrinsic sizes on the frames, and
+ // there are no dirty bits to set, then there's nothing to do.
+ if (dirtyType == nsIPresShell::eResize && !dirtyBits)
+ return;
+
+ nsIPresShell::ReflowRootHandling rootHandling;
+ if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
+ rootHandling = nsIPresShell::ePositionOrSizeChange;
+ } else {
+ rootHandling = nsIPresShell::eNoPositionOrSizeChange;
+ }
+
+ do {
+ aFrame->PresContext()->PresShell()->FrameNeedsReflow(
+ aFrame, dirtyType, dirtyBits, rootHandling);
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ } while (aFrame);
+}
+
+/* static */ nsIFrame*
+RestyleManagerBase::GetNextContinuationWithSameStyle(
+ nsIFrame* aFrame, nsStyleContext* aOldStyleContext,
+ bool* aHaveMoreContinuations)
+{
+ // See GetPrevContinuationWithSameStyle about {ib} splits.
+
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+ if (!nextContinuation &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // We're the last continuation, so we have to hop back to the first
+ // before getting the frame property
+ nextContinuation =
+ aFrame->FirstContinuation()->Properties().Get(nsIFrame::IBSplitSibling());
+ if (nextContinuation) {
+ nextContinuation =
+ nextContinuation->Properties().Get(nsIFrame::IBSplitSibling());
+ }
+ }
+
+ if (!nextContinuation) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(nextContinuation->GetContent() == aFrame->GetContent(),
+ "unexpected content mismatch");
+
+ nsStyleContext* nextStyle = nextContinuation->StyleContext();
+ if (nextStyle != aOldStyleContext) {
+ NS_ASSERTION(aOldStyleContext->GetPseudo() != nextStyle->GetPseudo() ||
+ aOldStyleContext->GetParent() != nextStyle->GetParent(),
+ "continuations should have the same style context");
+ nextContinuation = nullptr;
+ if (aHaveMoreContinuations) {
+ *aHaveMoreContinuations = true;
+ }
+ }
+ return nextContinuation;
+}
+
+nsresult
+RestyleManagerBase::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
+{
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+ if (aChangeList.IsEmpty())
+ return NS_OK;
+
+ PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames",
+ js::ProfileEntry::Category::CSS);
+
+ nsPresContext* presContext = PresContext();
+ FramePropertyTable* propTable = presContext->PropertyTable();
+ nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
+
+ // Make sure to not rebuild quote or counter lists while we're
+ // processing restyles
+ frameConstructor->BeginUpdate();
+
+ // Mark frames so that we skip frames that die along the way, bug 123049.
+ // A frame can be in the list multiple times with different hints. Further
+ // optmization is possible if nsStyleChangeList::AppendChange could coalesce
+ for (const nsStyleChangeData& data : aChangeList) {
+ if (data.mFrame) {
+ propTable->Set(data.mFrame, ChangeListProperty(), true);
+ }
+ }
+
+ bool didUpdateCursor = false;
+
+ for (const nsStyleChangeData& data : aChangeList) {
+ nsIFrame* frame = data.mFrame;
+ nsIContent* content = data.mContent;
+ nsChangeHint hint = data.mHint;
+ bool didReflowThisFrame = false;
+
+ NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
+ (hint & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ // skip any frame that has been destroyed due to a ripple effect
+ if (frame && !propTable->Get(frame, ChangeListProperty())) {
+ continue;
+ }
+
+ if (frame && frame->GetContent() != content) {
+ // XXXbz this is due to image maps messing with the primary frame of
+ // <area>s. See bug 135040. Remove this block once that's fixed.
+ frame = nullptr;
+ if (!(hint & nsChangeHint_ReconstructFrame)) {
+ continue;
+ }
+ }
+
+ if ((hint & nsChangeHint_UpdateContainingBlock) && frame &&
+ !(hint & nsChangeHint_ReconstructFrame)) {
+ if (NeedToReframeForAddingOrRemovingTransform(frame) ||
+ frame->GetType() == nsGkAtoms::fieldSetFrame ||
+ frame->GetContentInsertionFrame() != frame) {
+ // The frame has positioned children that need to be reparented, or
+ // it can't easily be converted to/from being an abs-pos container correctly.
+ hint |= nsChangeHint_ReconstructFrame;
+ } else {
+ for (nsIFrame* cont = frame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ // Normally frame construction would set state bits as needed,
+ // but we're not going to reconstruct the frame so we need to set them.
+ // It's because we need to set this state on each affected frame
+ // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
+ // to ancestors (i.e. it can't be an change hint that is handled for
+ // descendants).
+ if (cont->IsAbsPosContainingBlock()) {
+ if (!cont->IsAbsoluteContainer() &&
+ (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
+ cont->MarkAsAbsoluteContainingBlock();
+ }
+ } else {
+ if (cont->IsAbsoluteContainer()) {
+ if (cont->HasAbsolutelyPositionedChildren()) {
+ // If |cont| still has absolutely positioned children,
+ // we can't call MarkAsNotAbsoluteContainingBlock. This
+ // will remove a frame list that still has children in
+ // it that we need to keep track of.
+ // The optimization of removing it isn't particularly
+ // important, although it does mean we skip some tests.
+ NS_WARNING("skipping removal of absolute containing block");
+ } else {
+ cont->MarkAsNotAbsoluteContainingBlock();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ((hint & nsChangeHint_AddOrRemoveTransform) && frame &&
+ !(hint & nsChangeHint_ReconstructFrame)) {
+ for (nsIFrame* cont = frame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ if (cont->StyleDisplay()->HasTransform(cont)) {
+ cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+ // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
+ // transformed by other means. It's OK to have the bit even if it's
+ // not needed.
+ }
+ }
+
+ if (hint & nsChangeHint_ReconstructFrame) {
+ // If we ever start passing true here, be careful of restyles
+ // that involve a reframe and animations. In particular, if the
+ // restyle we're processing here is an animation restyle, but
+ // the style resolution we will do for the frame construction
+ // happens async when we're not in an animation restyle already,
+ // problems could arise.
+ // We could also have problems with triggering of CSS transitions
+ // on elements whose frames are reconstructed, since we depend on
+ // the reconstruction happening synchronously.
+ frameConstructor->RecreateFramesForContent(content, false,
+ nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr);
+ } else {
+ NS_ASSERTION(frame, "This shouldn't happen");
+
+ if (!frame->FrameMaintainsOverflow()) {
+ // frame does not maintain overflow rects, so avoid calling
+ // FinishAndStoreOverflow on it:
+ hint &= ~(nsChangeHint_UpdateOverflow |
+ nsChangeHint_ChildrenOnlyTransform |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateParentOverflow);
+ }
+
+ if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) {
+ // Frame can not be transformed, and thus a change in transform will
+ // have no effect and we should not use the
+ // nsChangeHint_UpdatePostTransformOverflow hint.
+ hint &= ~nsChangeHint_UpdatePostTransformOverflow;
+ }
+
+ if (hint & nsChangeHint_UpdateEffects) {
+ for (nsIFrame* cont = frame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ nsSVGEffects::UpdateEffects(cont);
+ }
+ }
+ if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
+ ((hint & nsChangeHint_UpdateOpacityLayer) &&
+ frame->IsFrameOfType(nsIFrame::eSVG) &&
+ !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
+ nsSVGEffects::InvalidateRenderingObservers(frame);
+ }
+ if (hint & nsChangeHint_NeedReflow) {
+ StyleChangeReflow(frame, hint);
+ didReflowThisFrame = true;
+ }
+
+ if ((hint & nsChangeHint_UpdateUsesOpacity) &&
+ frame->IsFrameOfType(nsIFrame::eTablePart)) {
+ NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
+ "should only return UpdateUsesOpacity hint "
+ "when also returning UpdateOpacityLayer hint");
+ // When an internal table part (including cells) changes between
+ // having opacity 1 and non-1, it changes whether its
+ // backgrounds (and those of table parts inside of it) are
+ // painted as part of the table's nsDisplayTableBorderBackground
+ // display item, or part of its own display item. That requires
+ // invalidation, so change UpdateOpacityLayer to RepaintFrame.
+ hint &= ~nsChangeHint_UpdateOpacityLayer;
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ // Opacity disables preserve-3d, so if we toggle it, then we also need
+ // to update the overflow areas of all potentially affected frames.
+ if ((hint & nsChangeHint_UpdateUsesOpacity) &&
+ frame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
+ hint |= nsChangeHint_UpdateSubtreeOverflow;
+ }
+
+ if (hint & nsChangeHint_UpdateBackgroundPosition) {
+ // For most frame types, DLBI can detect background position changes,
+ // so we only need to schedule a paint.
+ hint |= nsChangeHint_SchedulePaint;
+ if (frame->IsFrameOfType(nsIFrame::eTablePart) ||
+ frame->IsFrameOfType(nsIFrame::eMathML)) {
+ // Table parts and MathML frames don't build display items for their
+ // backgrounds, so DLBI can't detect background-position changes for
+ // these frames. Repaint the whole frame.
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView |
+ nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
+ ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
+ }
+ if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
+ ActiveLayerTracker::NotifyOffsetRestyle(frame);
+ // It is possible for this to fall back to a reflow
+ if (!RecomputePosition(frame)) {
+ didReflowThisFrame = true;
+ }
+ }
+ NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
+ (hint & nsChangeHint_UpdateOverflow),
+ "nsChangeHint_UpdateOverflow should be passed too");
+ if (!didReflowThisFrame &&
+ (hint & (nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateParentOverflow |
+ nsChangeHint_UpdateSubtreeOverflow))) {
+ if (hint & nsChangeHint_UpdateSubtreeOverflow) {
+ for (nsIFrame* cont = frame; cont; cont =
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
+ }
+ // The work we just did in AddSubtreeToOverflowTracker
+ // subsumes some of the other hints:
+ hint &= ~(nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow);
+ }
+ if (hint & nsChangeHint_ChildrenOnlyTransform) {
+ // The overflow areas of the child frames need to be updated:
+ nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
+ nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
+ NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
+ MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG),
+ "Not expecting non-SVG children");
+ // If |childFrame| is dirty or has dirty children, we don't bother
+ // updating overflows since that will happen when it's reflowed.
+ if (!(childFrame->GetStateBits() &
+ (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
+ mOverflowChangedTracker.AddFrame(childFrame,
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ NS_ASSERTION(childFrame->GetParent() == hintFrame,
+ "SVG child frame not expected to have different parent");
+ }
+ }
+ // If |frame| is dirty or has dirty children, we don't bother updating
+ // overflows since that will happen when it's reflowed.
+ if (!(frame->GetStateBits() &
+ (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
+ if (hint & (nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow)) {
+ OverflowChangedTracker::ChangeKind changeKind;
+ // If we have both nsChangeHint_UpdateOverflow and
+ // nsChangeHint_UpdatePostTransformOverflow,
+ // CHILDREN_CHANGED is selected as it is
+ // strictly stronger.
+ if (hint & nsChangeHint_UpdateOverflow) {
+ changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
+ } else {
+ changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
+ }
+ for (nsIFrame* cont = frame; cont; cont =
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ mOverflowChangedTracker.AddFrame(cont, changeKind);
+ }
+ }
+ // UpdateParentOverflow hints need to be processed in addition
+ // to the above, since if the processing of the above hints
+ // yields no change, the update will not propagate to the
+ // parent.
+ if (hint & nsChangeHint_UpdateParentOverflow) {
+ MOZ_ASSERT(frame->GetParent(),
+ "shouldn't get style hints for the root frame");
+ for (nsIFrame* cont = frame; cont; cont =
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ mOverflowChangedTracker.AddFrame(cont->GetParent(),
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ }
+ }
+ }
+ if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
+ presContext->PresShell()->SynthesizeMouseMove(false);
+ didUpdateCursor = true;
+ }
+ }
+ }
+
+ frameConstructor->EndUpdate();
+
+ // cleanup references and verify the style tree. Note that the latter needs
+ // to happen once we've processed the whole list, since until then the tree
+ // is not in fact in a consistent state.
+ for (const nsStyleChangeData& data : aChangeList) {
+ if (data.mFrame) {
+ propTable->Delete(data.mFrame, ChangeListProperty());
+ }
+
+#ifdef DEBUG
+ // reget frame from content since it may have been regenerated...
+ if (data.mContent) {
+ nsIFrame* frame = data.mContent->GetPrimaryFrame();
+ if (frame) {
+ DebugVerifyStyleTree(frame);
+ }
+ } else if (!data.mFrame ||
+ data.mFrame->GetType() != nsGkAtoms::viewportFrame) {
+ NS_WARNING("Unable to test style tree integrity -- no content node "
+ "(and not a viewport frame)");
+ }
+#endif
+ }
+
+ aChangeList.Clear();
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/base/RestyleManagerBase.h b/layout/base/RestyleManagerBase.h
new file mode 100644
index 000000000..f81f5e73f
--- /dev/null
+++ b/layout/base/RestyleManagerBase.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_RestyleManagerBase_h
+#define mozilla_RestyleManagerBase_h
+
+#include "mozilla/OverflowChangedTracker.h"
+#include "nsChangeHint.h"
+#include "nsPresContext.h"
+
+class nsCString;
+class nsCSSFrameConstructor;
+class nsStyleChangeList;
+
+namespace mozilla {
+
+class EventStates;
+class RestyleManager;
+class ServoRestyleManager;
+
+namespace dom {
+class Element;
+}
+
+/**
+ * Class for sharing data and logic common to both RestyleManager and
+ * ServoRestyleManager.
+ */
+class RestyleManagerBase
+{
+protected:
+ explicit RestyleManagerBase(nsPresContext* aPresContext);
+
+public:
+ typedef mozilla::dom::Element Element;
+
+ // Get an integer that increments every time we process pending restyles.
+ // The value is never 0.
+ uint32_t GetRestyleGeneration() const { return mRestyleGeneration; }
+
+ // Get an integer that increments every time there is a style change
+ // as a result of a change to the :hover content state.
+ uint32_t GetHoverGeneration() const { return mHoverGeneration; }
+
+ bool ObservingRefreshDriver() const { return mObservingRefreshDriver; }
+
+ void SetObservingRefreshDriver(bool aObserving) {
+ mObservingRefreshDriver = aObserving;
+ }
+
+ void Disconnect() { mPresContext = nullptr; }
+
+ static nsCString RestyleHintToString(nsRestyleHint aHint);
+
+#ifdef DEBUG
+ static nsCString ChangeHintToString(nsChangeHint aHint);
+
+ /**
+ * DEBUG ONLY method to verify integrity of style tree versus frame tree
+ */
+ static void DebugVerifyStyleTree(nsIFrame* aFrame);
+#endif
+
+ void FlushOverflowChangedTracker() {
+ mOverflowChangedTracker.Flush();
+ }
+
+ // Should be called when a frame is going to be destroyed and
+ // WillDestroyFrameTree hasn't been called yet.
+ void NotifyDestroyingFrame(nsIFrame* aFrame) {
+ mOverflowChangedTracker.RemoveFrame(aFrame);
+ }
+
+ // Note: It's the caller's responsibility to make sure to wrap a
+ // ProcessRestyledFrames call in a view update batch and a script blocker.
+ // This function does not call ProcessAttachedQueue() on the binding manager.
+ // If the caller wants that to happen synchronously, it needs to handle that
+ // itself.
+ nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList);
+
+protected:
+ void ContentStateChangedInternal(Element* aElement,
+ EventStates aStateMask,
+ nsChangeHint* aOutChangeHint,
+ nsRestyleHint* aOutRestyleHint);
+
+ bool IsDisconnected() { return mPresContext == nullptr; }
+
+ void IncrementHoverGeneration() {
+ ++mHoverGeneration;
+ }
+
+ void IncrementRestyleGeneration() {
+ if (++mRestyleGeneration == 0) {
+ // Keep mRestyleGeneration from being 0, since that's what
+ // nsPresContext::GetRestyleGeneration returns when it no
+ // longer has a RestyleManager.
+ ++mRestyleGeneration;
+ }
+ }
+
+ nsPresContext* PresContext() const {
+ MOZ_ASSERT(mPresContext);
+ return mPresContext;
+ }
+
+ nsCSSFrameConstructor* FrameConstructor() const {
+ return PresContext()->FrameConstructor();
+ }
+
+ inline bool IsGecko() const {
+ return !IsServo();
+ }
+
+ inline bool IsServo() const {
+#ifdef MOZ_STYLO
+ return PresContext()->StyleSet()->IsServo();
+#else
+ return false;
+#endif
+ }
+
+private:
+ nsPresContext* mPresContext; // weak, can be null after Disconnect().
+ uint32_t mRestyleGeneration;
+ uint32_t mHoverGeneration;
+ // True if we're already waiting for a refresh notification.
+ bool mObservingRefreshDriver;
+
+protected:
+ // True if we're in the middle of a nsRefreshDriver refresh
+ bool mInStyleRefresh;
+
+ OverflowChangedTracker mOverflowChangedTracker;
+
+ void PostRestyleEventInternal(bool aForLazyConstruction);
+
+ /**
+ * These are protected static methods that help with the change hint
+ * processing bits of the restyle managers.
+ */
+ static nsIFrame*
+ GetNearestAncestorFrame(nsIContent* aContent);
+
+ static nsIFrame*
+ GetNextBlockInInlineSibling(FramePropertyTable* aPropTable, nsIFrame* aFrame);
+
+ /**
+ * Get the next continuation or similar ib-split sibling (assuming
+ * block/inline alternation), conditionally on it having the same style.
+ *
+ * Since this is used when deciding to copy the new style context, it
+ * takes as an argument the old style context to check if the style is
+ * the same. When it is used in other contexts (i.e., where the next
+ * continuation would already have the new style context), the current
+ * style context should be passed.
+ */
+ static nsIFrame*
+ GetNextContinuationWithSameStyle(nsIFrame* aFrame,
+ nsStyleContext* aOldStyleContext,
+ bool* aHaveMoreContinuations = nullptr);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/RestyleManagerHandle.h b/layout/base/RestyleManagerHandle.h
new file mode 100644
index 000000000..8be04d12c
--- /dev/null
+++ b/layout/base/RestyleManagerHandle.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RestyleManagerHandle_h
+#define mozilla_RestyleManagerHandle_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/HandleRefPtr.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/StyleBackendType.h"
+#include "nsChangeHint.h"
+
+namespace mozilla {
+class RestyleManager;
+class ServoRestyleManager;
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+class nsAttrValue;
+class nsIAtom;
+class nsIContent;
+class nsIFrame;
+class nsStyleChangeList;
+
+namespace mozilla {
+
+#define SERVO_BIT 0x1
+
+/**
+ * Smart pointer class that can hold a pointer to either a RestyleManager
+ * or a ServoRestyleManager.
+ */
+class RestyleManagerHandle
+{
+public:
+ typedef HandleRefPtr<RestyleManagerHandle> RefPtr;
+
+ // We define this Ptr class with a RestyleManager API that forwards on to the
+ // wrapped pointer, rather than putting these methods on RestyleManagerHandle
+ // itself, so that we can have RestyleManagerHandle behave like a smart
+ // pointer and be dereferenced with operator->.
+ class Ptr
+ {
+ public:
+ friend class ::mozilla::RestyleManagerHandle;
+
+ bool IsGecko() const { return !IsServo(); }
+ bool IsServo() const
+ {
+ MOZ_ASSERT(mValue, "RestyleManagerHandle null pointer dereference");
+#ifdef MOZ_STYLO
+ return mValue & SERVO_BIT;
+#else
+ return false;
+#endif
+ }
+
+ StyleBackendType BackendType() const
+ {
+ return IsGecko() ? StyleBackendType::Gecko :
+ StyleBackendType::Servo;
+ }
+
+ RestyleManager* AsGecko()
+ {
+ MOZ_ASSERT(IsGecko());
+ return reinterpret_cast<RestyleManager*>(mValue);
+ }
+
+ ServoRestyleManager* AsServo()
+ {
+ MOZ_ASSERT(IsServo());
+ return reinterpret_cast<ServoRestyleManager*>(mValue & ~SERVO_BIT);
+ }
+
+ RestyleManager* GetAsGecko() { return IsGecko() ? AsGecko() : nullptr; }
+ ServoRestyleManager* GetAsServo() { return IsServo() ? AsServo() : nullptr; }
+
+ const RestyleManager* AsGecko() const
+ {
+ return const_cast<Ptr*>(this)->AsGecko();
+ }
+
+ const ServoRestyleManager* AsServo() const
+ {
+ MOZ_ASSERT(IsServo());
+ return const_cast<Ptr*>(this)->AsServo();
+ }
+
+ const RestyleManager* GetAsGecko() const { return IsGecko() ? AsGecko() : nullptr; }
+ const ServoRestyleManager* GetAsServo() const { return IsServo() ? AsServo() : nullptr; }
+
+ // These inline methods are defined in RestyleManagerHandleInlines.h.
+ inline MozExternalRefCountType AddRef();
+ inline MozExternalRefCountType Release();
+
+ // Restyle manager interface. These inline methods are defined in
+ // RestyleManagerHandleInlines.h and just forward to the underlying
+ // RestyleManager or ServoRestyleManager. See corresponding comments in
+ // RestyleManager.h for descriptions of these methods.
+
+ inline void Disconnect();
+ inline void PostRestyleEvent(dom::Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint);
+ inline void PostRestyleEventForLazyConstruction();
+ inline void RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+ inline void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+ inline void ProcessPendingRestyles();
+ inline void ContentInserted(nsINode* aContainer,
+ nsIContent* aChild);
+ inline void ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent);
+ inline void ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling);
+ inline void RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild);
+ inline void RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent);
+ inline nsresult ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask);
+ inline void AttributeWillChange(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue);
+ inline void AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue);
+ inline nsresult ReparentStyleContext(nsIFrame* aFrame);
+ inline bool HasPendingRestyles();
+ inline uint64_t GetRestyleGeneration() const;
+ inline uint32_t GetHoverGeneration() const;
+ inline void SetObservingRefreshDriver(bool aObserving);
+ inline nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList);
+ inline void FlushOverflowChangedTracker();
+ inline void NotifyDestroyingFrame(nsIFrame* aFrame);
+
+ private:
+ // Stores a pointer to an RestyleManager or a ServoRestyleManager. The least
+ // significant bit is 0 for the former, 1 for the latter. This is
+ // valid as the least significant bit will never be used for a pointer
+ // value on platforms we care about.
+ uintptr_t mValue;
+ };
+
+ MOZ_IMPLICIT RestyleManagerHandle(decltype(nullptr) = nullptr)
+ {
+ mPtr.mValue = 0;
+ }
+ RestyleManagerHandle(const RestyleManagerHandle& aOth)
+ {
+ mPtr.mValue = aOth.mPtr.mValue;
+ }
+ MOZ_IMPLICIT RestyleManagerHandle(RestyleManager* aManager)
+ {
+ *this = aManager;
+ }
+ MOZ_IMPLICIT RestyleManagerHandle(ServoRestyleManager* aManager)
+ {
+ *this = aManager;
+ }
+
+ RestyleManagerHandle& operator=(decltype(nullptr))
+ {
+ mPtr.mValue = 0;
+ return *this;
+ }
+
+ RestyleManagerHandle& operator=(RestyleManager* aManager)
+ {
+ MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aManager) & SERVO_BIT),
+ "least significant bit shouldn't be set; we use it for state");
+ mPtr.mValue = reinterpret_cast<uintptr_t>(aManager);
+ return *this;
+ }
+
+ RestyleManagerHandle& operator=(ServoRestyleManager* aManager)
+ {
+#ifdef MOZ_STYLO
+ MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aManager) & SERVO_BIT),
+ "least significant bit shouldn't be set; we use it for state");
+ mPtr.mValue =
+ aManager ? (reinterpret_cast<uintptr_t>(aManager) | SERVO_BIT) : 0;
+ return *this;
+#else
+ MOZ_CRASH("should not have a ServoRestyleManager object when MOZ_STYLO is "
+ "disabled");
+#endif
+ }
+
+ // Make RestyleManagerHandle usable in boolean contexts.
+ explicit operator bool() const { return !!mPtr.mValue; }
+ bool operator!() const { return !mPtr.mValue; }
+
+ // Make RestyleManagerHandle behave like a smart pointer.
+ Ptr* operator->() { return &mPtr; }
+ const Ptr* operator->() const { return &mPtr; }
+
+private:
+ Ptr mPtr;
+};
+
+#undef SERVO_BIT
+
+} // namespace mozilla
+
+#endif // mozilla_RestyleManagerHandle_h
diff --git a/layout/base/RestyleManagerHandleInlines.h b/layout/base/RestyleManagerHandleInlines.h
new file mode 100644
index 000000000..cc374edd5
--- /dev/null
+++ b/layout/base/RestyleManagerHandleInlines.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RestyleManagerHandleInlines_h
+#define mozilla_RestyleManagerHandleInlines_h
+
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoRestyleManager.h"
+
+#define FORWARD_CONCRETE(method_, geckoargs_, servoargs_) \
+ if (IsGecko()) { \
+ return AsGecko()->method_ geckoargs_; \
+ } else { \
+ return AsServo()->method_ servoargs_; \
+ }
+
+#define FORWARD(method_, args_) FORWARD_CONCRETE(method_, args_, args_)
+
+namespace mozilla {
+
+MozExternalRefCountType
+RestyleManagerHandle::Ptr::AddRef()
+{
+ FORWARD(AddRef, ());
+}
+
+MozExternalRefCountType
+RestyleManagerHandle::Ptr::Release()
+{
+ FORWARD(Release, ());
+}
+
+void
+RestyleManagerHandle::Ptr::Disconnect()
+{
+ FORWARD(Disconnect, ());
+}
+
+void
+RestyleManagerHandle::Ptr::PostRestyleEvent(dom::Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint)
+{
+ FORWARD(PostRestyleEvent, (aElement, aRestyleHint, aMinChangeHint));
+}
+
+void
+RestyleManagerHandle::Ptr::PostRestyleEventForLazyConstruction()
+{
+ FORWARD(PostRestyleEventForLazyConstruction, ());
+}
+
+void
+RestyleManagerHandle::Ptr::RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ FORWARD(RebuildAllStyleData, (aExtraHint, aRestyleHint));
+}
+
+void
+RestyleManagerHandle::Ptr::PostRebuildAllStyleDataEvent(
+ nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ FORWARD(PostRebuildAllStyleDataEvent, (aExtraHint, aRestyleHint));
+}
+
+void
+RestyleManagerHandle::Ptr::ProcessPendingRestyles()
+{
+ FORWARD(ProcessPendingRestyles, ());
+}
+
+nsresult
+RestyleManagerHandle::Ptr::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
+{
+ FORWARD(ProcessRestyledFrames, (aChangeList));
+}
+
+void
+RestyleManagerHandle::Ptr::FlushOverflowChangedTracker()
+{
+ FORWARD(FlushOverflowChangedTracker, ());
+}
+
+void
+RestyleManagerHandle::Ptr::ContentInserted(nsINode* aContainer,
+ nsIContent* aChild)
+{
+ FORWARD(ContentInserted, (aContainer, aChild));
+}
+
+void
+RestyleManagerHandle::Ptr::ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ FORWARD(ContentAppended, (aContainer, aFirstNewContent));
+}
+
+void
+RestyleManagerHandle::Ptr::ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling)
+{
+ FORWARD(ContentRemoved, (aContainer, aOldChild, aFollowingSibling));
+}
+
+void
+RestyleManagerHandle::Ptr::RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild)
+{
+ FORWARD(RestyleForInsertOrChange, (aContainer, aChild));
+}
+
+void
+RestyleManagerHandle::Ptr::RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ FORWARD(RestyleForAppend, (aContainer, aFirstNewContent));
+}
+
+nsresult
+RestyleManagerHandle::Ptr::ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask)
+{
+ FORWARD(ContentStateChanged, (aContent, aStateMask));
+}
+
+void
+RestyleManagerHandle::Ptr::AttributeWillChange(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ FORWARD(AttributeWillChange, (aElement, aNameSpaceID, aAttribute, aModType,
+ aNewValue));
+}
+
+void
+RestyleManagerHandle::Ptr::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ FORWARD(AttributeChanged, (aElement, aNameSpaceID, aAttribute, aModType,
+ aOldValue));
+}
+
+nsresult
+RestyleManagerHandle::Ptr::ReparentStyleContext(nsIFrame* aFrame)
+{
+ FORWARD(ReparentStyleContext, (aFrame));
+}
+
+bool
+RestyleManagerHandle::Ptr::HasPendingRestyles()
+{
+ FORWARD(HasPendingRestyles, ());
+}
+
+uint64_t
+RestyleManagerHandle::Ptr::GetRestyleGeneration() const
+{
+ FORWARD(GetRestyleGeneration, ());
+}
+
+uint32_t
+RestyleManagerHandle::Ptr::GetHoverGeneration() const
+{
+ FORWARD(GetHoverGeneration, ());
+}
+
+void
+RestyleManagerHandle::Ptr::SetObservingRefreshDriver(bool aObserving)
+{
+ FORWARD(SetObservingRefreshDriver, (aObserving));
+}
+
+void
+RestyleManagerHandle::Ptr::NotifyDestroyingFrame(nsIFrame* aFrame)
+{
+ FORWARD(NotifyDestroyingFrame, (aFrame));
+}
+
+} // namespace mozilla
+
+#undef FORWARD
+#undef FORWARD_CONCRETE
+
+#endif // mozilla_RestyleManagerHandleInlines_h
diff --git a/layout/base/RestyleTracker.cpp b/layout/base/RestyleTracker.cpp
new file mode 100644
index 000000000..7d68058d1
--- /dev/null
+++ b/layout/base/RestyleTracker.cpp
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A class which manages pending restyles. This handles keeping track
+ * of what nodes restyles need to happen on and so forth.
+ */
+
+#include "RestyleTracker.h"
+
+#include "GeckoProfiler.h"
+#include "nsDocShell.h"
+#include "nsFrameManager.h"
+#include "nsIDocument.h"
+#include "nsStyleChangeList.h"
+#include "mozilla/RestyleManager.h"
+#include "RestyleTrackerInlines.h"
+#include "nsTransitionManager.h"
+#include "mozilla/RestyleTimelineMarker.h"
+
+namespace mozilla {
+
+#ifdef RESTYLE_LOGGING
+static nsCString
+GetDocumentURI(nsIDocument* aDocument)
+{
+ nsCString result;
+ nsAutoString url;
+ nsresult rv = aDocument->GetDocumentURI(url);
+ if (NS_SUCCEEDED(rv)) {
+ result.Append(NS_ConvertUTF16toUTF8(url).get());
+ }
+
+ return result;
+}
+
+static nsCString
+FrameTagToString(dom::Element* aElement)
+{
+ nsCString result;
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (frame) {
+ nsFrame::ListTag(result, frame);
+ } else {
+ nsAutoString buf;
+ aElement->NodeInfo()->NameAtom()->ToString(buf);
+ result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
+ }
+ return result;
+}
+#endif
+
+inline nsIDocument*
+RestyleTracker::Document() const {
+ return mRestyleManager->PresContext()->Document();
+}
+
+#define RESTYLE_ARRAY_STACKSIZE 128
+
+struct RestyleEnumerateData : RestyleTracker::Hints {
+ RefPtr<dom::Element> mElement;
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ UniquePtr<ProfilerBacktrace> mBacktrace;
+#endif
+};
+
+inline void
+RestyleTracker::ProcessOneRestyle(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0,
+ "Someone should have handled this before calling us");
+ NS_PRECONDITION(Document(), "Must have a document");
+ NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
+ "Element has unexpected document");
+
+ LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
+ RestyleManager::RestyleHintToString(aRestyleHint).get(),
+ RestyleManager::ChangeHintToString(aChangeHint).get());
+
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
+ if (aRestyleHint & ~eRestyle_LaterSiblings) {
+#ifdef RESTYLE_LOGGING
+ if (ShouldLogRestyle() && primaryFrame &&
+ RestyleManager::StructsToLog() != 0) {
+ LOG_RESTYLE("style context tree before restyle:");
+ LOG_RESTYLE_INDENT();
+ primaryFrame->StyleContext()->LogStyleContextTree(
+ LoggingDepth(), RestyleManager::StructsToLog());
+ }
+#endif
+ mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
+ *this, aRestyleHint, aRestyleHintData);
+ } else if (aChangeHint &&
+ (primaryFrame ||
+ (aChangeHint & nsChangeHint_ReconstructFrame))) {
+ // Don't need to recompute style; just apply the hint
+ nsStyleChangeList changeList;
+ changeList.AppendChange(primaryFrame, aElement, aChangeHint);
+ mRestyleManager->ProcessRestyledFrames(changeList);
+ }
+}
+
+void
+RestyleTracker::DoProcessRestyles()
+{
+ nsAutoCString docURL("N/A");
+ if (profiler_is_active()) {
+ nsIURI *uri = Document()->GetDocumentURI();
+ if (uri) {
+ docURL = uri->GetSpecOrDefault();
+ }
+ }
+ PROFILER_LABEL_PRINTF("RestyleTracker", "ProcessRestyles",
+ js::ProfileEntry::Category::CSS, "(%s)", docURL.get());
+
+ nsDocShell* docShell = static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
+
+ // Create a AnimationsWithDestroyedFrame during restyling process to
+ // stop animations and transitions on elements that have no frame at the end
+ // of the restyling process.
+ RestyleManager::AnimationsWithDestroyedFrame
+ animationsWithDestroyedFrame(mRestyleManager);
+
+ // Create a ReframingStyleContexts struct on the stack and put it in our
+ // mReframingStyleContexts for almost all of the remaining scope of
+ // this function.
+ //
+ // It needs to be *in* scope during BeginProcessingRestyles, which
+ // might (if mDoRebuildAllStyleData is true) do substantial amounts of
+ // restyle processing.
+ //
+ // However, it needs to be *out* of scope during
+ // EndProcessingRestyles, since we should release the style contexts
+ // it holds prior to any EndReconstruct call that
+ // EndProcessingRestyles makes. This is because in EndReconstruct we
+ // try to destroy the old rule tree using the GC mechanism, which
+ // means it only gets destroyed if it's unreferenced (and if it's
+ // referenced, we assert). So we want the ReframingStyleContexts
+ // (which holds old style contexts) to be destroyed before the
+ // EndReconstruct so those style contexts go away before
+ // EndReconstruct.
+ {
+ RestyleManager::ReframingStyleContexts
+ reframingStyleContexts(mRestyleManager);
+
+ mRestyleManager->BeginProcessingRestyles(*this);
+
+ LOG_RESTYLE("Processing %d pending %srestyles with %d restyle roots for %s",
+ mPendingRestyles.Count(),
+ mRestyleManager->PresContext()->TransitionManager()->
+ InAnimationOnlyStyleUpdate()
+ ? (const char*) "animation " : (const char*) "",
+ static_cast<int>(mRestyleRoots.Length()),
+ GetDocumentURI(Document()).get());
+ LOG_RESTYLE_INDENT();
+
+ // loop so that we process any restyle events generated by processing
+ while (mPendingRestyles.Count()) {
+ if (mHaveLaterSiblingRestyles) {
+ // Convert them to individual restyles on all the later siblings
+ AutoTArray<RefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
+ for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<dom::Element*>(iter.Key());
+ MOZ_ASSERT(!element->IsStyledByServo(),
+ "Should not have Servo-styled elements here");
+ // Only collect the entries that actually need restyling by us (and
+ // haven't, for example, already been restyled).
+ // It's important to not mess with the flags on entries not in our
+ // document.
+ if (element->GetComposedDoc() == Document() &&
+ element->HasFlag(RestyleBit()) &&
+ (iter.Data()->mRestyleHint & eRestyle_LaterSiblings)) {
+ laterSiblingArr.AppendElement(element);
+ }
+ }
+ for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
+ Element* element = laterSiblingArr[i];
+ MOZ_ASSERT(!element->IsStyledByServo());
+ for (nsIContent* sibling = element->GetNextSibling();
+ sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (sibling->IsElement()) {
+ LOG_RESTYLE("adding pending restyle for %s due to "
+ "eRestyle_LaterSiblings hint on %s",
+ FrameTagToString(sibling->AsElement()).get(),
+ FrameTagToString(element->AsElement()).get());
+ if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0))) {
+ // Nothing else to do here; we'll handle the following
+ // siblings when we get to |sibling| in laterSiblingArr.
+ break;
+ }
+ }
+ }
+ }
+
+ // Now remove all those eRestyle_LaterSiblings bits
+ for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
+ Element* element = laterSiblingArr[i];
+ NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?");
+ RestyleData* data;
+#ifdef DEBUG
+ bool found =
+#endif
+ mPendingRestyles.Get(element, &data);
+ NS_ASSERTION(found, "Where did our entry go?");
+ data->mRestyleHint =
+ nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
+ }
+
+ LOG_RESTYLE("%d pending restyles after expanding out "
+ "eRestyle_LaterSiblings", mPendingRestyles.Count());
+
+ mHaveLaterSiblingRestyles = false;
+ }
+
+ uint32_t rootCount;
+ while ((rootCount = mRestyleRoots.Length())) {
+ // Make sure to pop the element off our restyle root array, so
+ // that we can freely append to the array as we process this
+ // element.
+ RefPtr<Element> element;
+ element.swap(mRestyleRoots[rootCount - 1]);
+ mRestyleRoots.RemoveElementAt(rootCount - 1);
+
+ LOG_RESTYLE("processing style root %s at index %d",
+ FrameTagToString(element).get(), rootCount - 1);
+ LOG_RESTYLE_INDENT();
+
+ // Do the document check before calling GetRestyleData, since we
+ // don't want to do the sibling-processing GetRestyleData does if
+ // the node is no longer relevant.
+ if (element->GetComposedDoc() != Document()) {
+ // Content node has been removed from our document; nothing else
+ // to do here
+ LOG_RESTYLE("skipping, no longer in the document");
+ continue;
+ }
+
+ nsAutoPtr<RestyleData> data;
+ if (!GetRestyleData(element, data)) {
+ LOG_RESTYLE("skipping, already restyled");
+ continue;
+ }
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ data->mRestyleHint, MarkerTracingType::START)));
+ }
+
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ Maybe<GeckoProfilerTracingRAII> profilerRAII;
+ if (profiler_feature_active("restyle")) {
+ profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace));
+ }
+#endif
+ ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
+ data->mRestyleHintData);
+ AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ data->mRestyleHint, MarkerTracingType::END)));
+ }
+ }
+
+ if (mHaveLaterSiblingRestyles) {
+ // Keep processing restyles for now
+ continue;
+ }
+
+ // Now we only have entries with change hints left. To be safe in
+ // case of reentry from the handing of the change hint, use a
+ // scratch array instead of calling out to ProcessOneRestyle while
+ // enumerating the hashtable. Use the stack if we can, otherwise
+ // fall back on heap-allocation.
+ AutoTArray<RestyleEnumerateData, RESTYLE_ARRAY_STACKSIZE> restyleArr;
+ RestyleEnumerateData* restylesToProcess =
+ restyleArr.AppendElements(mPendingRestyles.Count());
+ if (restylesToProcess) {
+ RestyleEnumerateData* restyle = restylesToProcess;
+#ifdef RESTYLE_LOGGING
+ uint32_t count = 0;
+#endif
+ for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<dom::Element*>(iter.Key());
+ RestyleTracker::RestyleData* data = iter.Data();
+
+ // Only collect the entries that actually need restyling by us (and
+ // haven't, for example, already been restyled).
+ // It's important to not mess with the flags on entries not in our
+ // document.
+ if (element->GetComposedDoc() != Document() ||
+ !element->HasFlag(RestyleBit())) {
+ LOG_RESTYLE("skipping pending restyle %s, already restyled or no "
+ "longer in the document",
+ FrameTagToString(element).get());
+ continue;
+ }
+
+ NS_ASSERTION(
+ !element->HasFlag(RootBit()) ||
+ // Maybe we're just not reachable via the frame tree?
+ (element->GetFlattenedTreeParent() &&
+ (!element->GetFlattenedTreeParent()->GetPrimaryFrame() ||
+ element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf() ||
+ element->GetComposedDoc()->GetShell()->FrameManager()
+ ->GetDisplayContentsStyleFor(element))) ||
+ // Or not reachable due to an async reinsert we have
+ // pending? If so, we'll have a reframe hint around.
+ // That incidentally makes it safe that we still have
+ // the bit, since any descendants that didn't get added
+ // to the roots list because we had the bits will be
+ // completely restyled in a moment.
+ (data->mChangeHint & nsChangeHint_ReconstructFrame),
+ "Why did this not get handled while processing mRestyleRoots?");
+
+ // Unset the restyle bits now, so if they get readded later as we
+ // process we won't clobber that adding of the bit.
+ element->UnsetFlags(RestyleBit() |
+ RootBit() |
+ ConditionalDescendantsBit());
+
+ restyle->mElement = element;
+ restyle->mRestyleHint = data->mRestyleHint;
+ restyle->mChangeHint = data->mChangeHint;
+ // We can move data since we'll be clearing mPendingRestyles after
+ // we finish enumerating it.
+ restyle->mRestyleHintData = Move(data->mRestyleHintData);
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ restyle->mBacktrace = Move(data->mBacktrace);
+#endif
+
+#ifdef RESTYLE_LOGGING
+ count++;
+#endif
+
+ // Increment to the next slot in the array
+ restyle++;
+ }
+
+ RestyleEnumerateData* lastRestyle = restyle;
+
+ // Clear the hashtable now that we don't need it anymore
+ mPendingRestyles.Clear();
+
+#ifdef RESTYLE_LOGGING
+ uint32_t index = 0;
+#endif
+ for (RestyleEnumerateData* currentRestyle = restylesToProcess;
+ currentRestyle != lastRestyle;
+ ++currentRestyle) {
+ LOG_RESTYLE("processing pending restyle %s at index %d/%d",
+ FrameTagToString(currentRestyle->mElement).get(),
+ index++, count);
+ LOG_RESTYLE_INDENT();
+
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ Maybe<GeckoProfilerTracingRAII> profilerRAII;
+ if (profiler_feature_active("restyle")) {
+ profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
+ }
+#endif
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ currentRestyle->mRestyleHint, MarkerTracingType::START)));
+ }
+
+ ProcessOneRestyle(currentRestyle->mElement,
+ currentRestyle->mRestyleHint,
+ currentRestyle->mChangeHint,
+ currentRestyle->mRestyleHintData);
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ currentRestyle->mRestyleHint, MarkerTracingType::END)));
+ }
+ }
+ }
+ }
+ }
+
+ // mPendingRestyles is now empty.
+ mHaveSelectors = false;
+
+ mRestyleManager->EndProcessingRestyles();
+}
+
+bool
+RestyleTracker::GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData)
+{
+ NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
+ "Unexpected document; this will lead to incorrect behavior!");
+
+ if (!aElement->HasFlag(RestyleBit())) {
+ NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?");
+ return false;
+ }
+
+ mPendingRestyles.RemoveAndForget(aElement, aData);
+ NS_ASSERTION(aData.get(), "Must have data if restyle bit is set");
+
+ if (aData->mRestyleHint & eRestyle_LaterSiblings) {
+ // Someone readded the eRestyle_LaterSiblings hint for this
+ // element. Leave it around for now, but remove the other restyle
+ // hints and the change hint for it. Also unset its root bit,
+ // since it's no longer a root with the new restyle data.
+
+ // During a normal restyle, we should have already processed the
+ // mDescendants array the last time we processed the restyle
+ // for this element. But in RebuildAllStyleData, we don't initially
+ // expand out eRestyle_LaterSiblings, so we can get in here the
+ // first time we need to process a restyle for this element. In that
+ // case, it's fine for us to have a non-empty mDescendants, since
+ // we know that RebuildAllStyleData adds eRestyle_ForceDescendants
+ // and we're guaranteed we'll restyle the entire tree.
+ NS_ASSERTION(mRestyleManager->InRebuildAllStyleData() ||
+ aData->mDescendants.IsEmpty(),
+ "expected descendants to be handled by now");
+
+ RestyleData* newData = new RestyleData;
+ newData->mChangeHint = nsChangeHint(0);
+ newData->mRestyleHint = eRestyle_LaterSiblings;
+ mPendingRestyles.Put(aElement, newData);
+ aElement->UnsetFlags(RootBit());
+ aData->mRestyleHint =
+ nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings);
+ } else {
+ aElement->UnsetFlags(mRestyleBits);
+ }
+
+ return true;
+}
+
+void
+RestyleTracker::AddRestyleRootsIfAwaitingRestyle(
+ const nsTArray<RefPtr<Element>>& aElements)
+{
+ // The RestyleData for a given element has stored in mDescendants
+ // the list of descendants we need to end up restyling. Since we
+ // won't necessarily end up restyling them, due to the restyle
+ // process finishing early (see how RestyleResult::eStop is handled
+ // in ElementRestyler::Restyle), we add them to the list of restyle
+ // roots to handle the next time around the
+ // RestyleTracker::DoProcessRestyles loop.
+ //
+ // Note that aElements must maintain the same invariant
+ // that mRestyleRoots does, i.e. that ancestors appear after descendants.
+ // Since we call AddRestyleRootsIfAwaitingRestyle only after we have
+ // removed the restyle root we are currently processing from the end of
+ // mRestyleRoots, and the only elements we get here in aElements are
+ // descendants of that restyle root, we are safe to simply append to the
+ // end of mRestyleRoots to maintain its invariant.
+ for (size_t i = 0; i < aElements.Length(); i++) {
+ Element* element = aElements[i];
+ if (element->HasFlag(RestyleBit())) {
+ mRestyleRoots.AppendElement(element);
+ }
+ }
+}
+
+void
+RestyleTracker::ClearSelectors()
+{
+ if (!mHaveSelectors) {
+ return;
+ }
+ for (auto it = mPendingRestyles.Iter(); !it.Done(); it.Next()) {
+ RestyleData* data = it.Data();
+ if (data->mRestyleHint & eRestyle_SomeDescendants) {
+ data->mRestyleHint =
+ (data->mRestyleHint & ~eRestyle_SomeDescendants) | eRestyle_Subtree;
+ data->mRestyleHintData.mSelectorsForDescendants.Clear();
+ } else {
+ MOZ_ASSERT(data->mRestyleHintData.mSelectorsForDescendants.IsEmpty());
+ }
+ }
+ mHaveSelectors = false;
+}
+
+} // namespace mozilla
diff --git a/layout/base/RestyleTracker.h b/layout/base/RestyleTracker.h
new file mode 100644
index 000000000..10a653cdb
--- /dev/null
+++ b/layout/base/RestyleTracker.h
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A class which manages pending restyles. This handles keeping track
+ * of what nodes restyles need to happen on and so forth.
+ */
+
+#ifndef mozilla_RestyleTracker_h
+#define mozilla_RestyleTracker_h
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/OverflowChangedTracker.h"
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsContainerFrame.h"
+#include "nsIContentInlines.h"
+#include "mozilla/SplayTree.h"
+#include "mozilla/RestyleLogging.h"
+#include "GeckoProfiler.h"
+#include "mozilla/Maybe.h"
+
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+#include "ProfilerBacktrace.h"
+#endif
+
+namespace mozilla {
+
+class RestyleManager;
+class ElementRestyler;
+
+class RestyleTracker {
+public:
+ typedef mozilla::dom::Element Element;
+
+ friend class ElementRestyler; // for AddPendingRestyleToTable
+
+ explicit RestyleTracker(Element::FlagsType aRestyleBits)
+ : mRestyleBits(aRestyleBits)
+ , mHaveLaterSiblingRestyles(false)
+ , mHaveSelectors(false)
+ {
+ NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
+ "Why do we have these bits set?");
+ NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
+ "Must have a restyle flag");
+ NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
+ ELEMENT_PENDING_RESTYLE_FLAGS,
+ "Shouldn't have both restyle flags set");
+ NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) != 0,
+ "Must have root flag");
+ NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) !=
+ ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS,
+ "Shouldn't have both root flags");
+ }
+
+ void Init(RestyleManager* aRestyleManager) {
+ mRestyleManager = aRestyleManager;
+ }
+
+ uint32_t Count() const {
+ return mPendingRestyles.Count();
+ }
+
+ /**
+ * Add a restyle for the given element to the tracker. Returns true
+ * if the element already had eRestyle_LaterSiblings set on it.
+ *
+ * aRestyleRoot is the closest restyle root for aElement. If the caller
+ * does not know what the closest restyle root is, Nothing should be
+ * passed. A Some(nullptr) restyle root can be passed if there is no
+ * ancestor element that is a restyle root.
+ */
+ bool AddPendingRestyle(Element* aElement, nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData = nullptr,
+ mozilla::Maybe<Element*> aRestyleRoot =
+ mozilla::Nothing());
+
+ Element* FindClosestRestyleRoot(Element* aElement);
+
+ /**
+ * Process the restyles we've been tracking.
+ */
+ void DoProcessRestyles();
+
+ // Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit
+ uint32_t RestyleBit() const {
+ return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
+ }
+
+ // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
+ Element::FlagsType RootBit() const {
+ return mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS;
+ }
+
+ // Return our ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR bit if present,
+ // or 0 if it is not.
+ Element::FlagsType ConditionalDescendantsBit() const {
+ return mRestyleBits & ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR;
+ }
+
+ struct Hints {
+ nsRestyleHint mRestyleHint; // What we want to restyle
+ nsChangeHint mChangeHint; // The minimal change hint for "self"
+ RestyleHintData mRestyleHintData; // Data associated with mRestyleHint
+ };
+
+ struct RestyleData : Hints {
+ RestyleData() {
+ mRestyleHint = nsRestyleHint(0);
+ mChangeHint = nsChangeHint(0);
+ }
+
+ RestyleData(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint,
+ const RestyleHintData* aRestyleHintData) {
+ mRestyleHint = aRestyleHint;
+ mChangeHint = aChangeHint;
+ if (aRestyleHintData) {
+ mRestyleHintData = *aRestyleHintData;
+ }
+ }
+
+ // Descendant elements we must check that we ended up restyling, ordered
+ // with the same invariant as mRestyleRoots. The elements here are those
+ // that we called AddPendingRestyle for and found the element this is
+ // the RestyleData for as its nearest restyle root.
+ nsTArray<RefPtr<Element>> mDescendants;
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ UniquePtr<ProfilerBacktrace> mBacktrace;
+#endif
+ };
+
+ /**
+ * If the given Element has a restyle pending for it, return the
+ * relevant restyle data. This function will clear everything other
+ * than a possible eRestyle_LaterSiblings hint for aElement out of
+ * our hashtable. The returned aData will never have an
+ * eRestyle_LaterSiblings hint in it.
+ *
+ * The return value indicates whether any restyle data was found for
+ * the element. aData is set to nullptr iff false is returned.
+ */
+ bool GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData);
+
+ /**
+ * Returns whether there is a RestyleData entry in mPendingRestyles
+ * for the given element.
+ */
+ bool HasRestyleData(Element* aElement) {
+ return mPendingRestyles.Contains(aElement);
+ }
+
+ /**
+ * For each element in aElements, appends it to mRestyleRoots if it
+ * has its restyle bit set. This is used to ensure we restyle elements
+ * that we did not add as restyle roots initially (due to there being
+ * an ancestor with the restyle root bit set), but which we might
+ * not have got around to restyling due to the restyle process
+ * terminating early with RestyleResul::eStop (see ElementRestyler::Restyle).
+ *
+ * This function must be called with elements in order such that
+ * appending them to mRestyleRoots maintains its ordering invariant that
+ * ancestors appear after descendants.
+ */
+ void AddRestyleRootsIfAwaitingRestyle(
+ const nsTArray<RefPtr<Element>>& aElements);
+
+ /**
+ * Converts any eRestyle_SomeDescendants restyle hints in the pending restyle
+ * table into eRestyle_Subtree hints and clears out the associated arrays of
+ * nsCSSSelector pointers. This is called in response to a style sheet change
+ * that might have cause an nsCSSSelector to be destroyed.
+ */
+ void ClearSelectors();
+
+ /**
+ * The document we're associated with.
+ */
+ inline nsIDocument* Document() const;
+
+#ifdef RESTYLE_LOGGING
+ // Defined in RestyleTrackerInlines.h.
+ inline bool ShouldLogRestyle();
+ inline int32_t& LoggingDepth();
+#endif
+
+private:
+ bool AddPendingRestyleToTable(Element* aElement, nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData = nullptr);
+
+ /**
+ * Handle a single mPendingRestyles entry. aRestyleHint must not
+ * include eRestyle_LaterSiblings; that needs to be dealt with
+ * before calling this function.
+ */
+ inline void ProcessOneRestyle(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint,
+ const RestyleHintData& aRestyleHintData);
+
+ typedef nsClassHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable;
+ typedef AutoTArray< RefPtr<Element>, 32> RestyleRootArray;
+ // Our restyle bits. These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and
+ // will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS, one flag
+ // from ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS, and might also include
+ // ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR.
+ Element::FlagsType mRestyleBits;
+ RestyleManager* mRestyleManager; // Owns us
+ // A hashtable that maps elements to pointers to RestyleData structs. The
+ // values only make sense if the element's current document is our
+ // document and it has our RestyleBit() flag set. In particular,
+ // said bit might not be set if the element had a restyle posted and
+ // then was moved around in the DOM.
+ PendingRestyleTable mPendingRestyles;
+ // An array that keeps track of our possible restyle roots. This
+ // maintains the invariant that if A and B are both restyle roots
+ // and A is an ancestor of B then A will come after B in the array.
+ // We maintain this invariant by checking whether an element has an
+ // ancestor with the restyle root bit set before appending it to the
+ // array.
+ RestyleRootArray mRestyleRoots;
+ // True if we have some entries with the eRestyle_LaterSiblings
+ // flag. We need this to avoid enumerating the hashtable looking
+ // for such entries when we can't possibly have any.
+ bool mHaveLaterSiblingRestyles;
+ // True if we have some entries with selectors in the restyle hint data.
+ // We use this to skip iterating over mPendingRestyles in ClearSelectors.
+ bool mHaveSelectors;
+};
+
+inline bool
+RestyleTracker::AddPendingRestyleToTable(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData)
+{
+ RestyleData* existingData;
+
+ if (aRestyleHintData &&
+ !aRestyleHintData->mSelectorsForDescendants.IsEmpty()) {
+ mHaveSelectors = true;
+ }
+
+ // Check the RestyleBit() flag before doing the hashtable Get, since
+ // it's possible that the data in the hashtable isn't actually
+ // relevant anymore (if the flag is not set).
+ if (aElement->HasFlag(RestyleBit())) {
+ mPendingRestyles.Get(aElement, &existingData);
+ } else {
+ aElement->SetFlags(RestyleBit());
+ existingData = nullptr;
+ }
+
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ NS_ASSERTION(ConditionalDescendantsBit(),
+ "why are we getting eRestyle_SomeDescendants in an "
+ "animation-only restyle?");
+ aElement->SetFlags(ConditionalDescendantsBit());
+ }
+
+ if (!existingData) {
+ RestyleData* rd =
+ new RestyleData(aRestyleHint, aMinChangeHint, aRestyleHintData);
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ if (profiler_feature_active("restyle")) {
+ rd->mBacktrace.reset(profiler_get_backtrace());
+ }
+#endif
+ mPendingRestyles.Put(aElement, rd);
+ return false;
+ }
+
+ bool hadRestyleLaterSiblings =
+ (existingData->mRestyleHint & eRestyle_LaterSiblings) != 0;
+ existingData->mRestyleHint =
+ nsRestyleHint(existingData->mRestyleHint | aRestyleHint);
+ existingData->mChangeHint |= aMinChangeHint;
+ if (aRestyleHintData) {
+ existingData->mRestyleHintData.mSelectorsForDescendants
+ .AppendElements(aRestyleHintData->mSelectorsForDescendants);
+ }
+
+ return hadRestyleLaterSiblings;
+}
+
+inline mozilla::dom::Element*
+RestyleTracker::FindClosestRestyleRoot(Element* aElement)
+{
+ Element* cur = aElement;
+ while (!cur->HasFlag(RootBit())) {
+ nsIContent* parent = cur->GetFlattenedTreeParent();
+ // Stop if we have no parent or the parent is not an element or
+ // we're part of the viewport scrollbars (because those are not
+ // frametree descendants of the primary frame of the root
+ // element).
+ // XXXbz maybe the primary frame of the root should be the root scrollframe?
+ if (!parent || !parent->IsElement() ||
+ // If we've hit the root via a native anonymous kid and that
+ // this native anonymous kid is not obviously a descendant
+ // of the root's primary frame, assume we're under the root
+ // scrollbars. Since those don't get reresolved when
+ // reresolving the root, we need to make sure to add the
+ // element to mRestyleRoots.
+ (cur->IsInNativeAnonymousSubtree() && !parent->GetParent() &&
+ cur->GetPrimaryFrame() &&
+ cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) {
+ return nullptr;
+ }
+ cur = parent->AsElement();
+ }
+ return cur;
+}
+
+inline bool
+RestyleTracker::AddPendingRestyle(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData,
+ mozilla::Maybe<Element*> aRestyleRoot)
+{
+ bool hadRestyleLaterSiblings =
+ AddPendingRestyleToTable(aElement, aRestyleHint, aMinChangeHint,
+ aRestyleHintData);
+
+ // We can only treat this element as a restyle root if we would
+ // actually restyle its descendants (so either call
+ // ElementRestyler::Restyle on it or just reframe it).
+ if ((aRestyleHint & ~eRestyle_LaterSiblings) ||
+ (aMinChangeHint & nsChangeHint_ReconstructFrame)) {
+ Element* cur =
+ aRestyleRoot ? *aRestyleRoot : FindClosestRestyleRoot(aElement);
+ if (!cur) {
+ mRestyleRoots.AppendElement(aElement);
+ cur = aElement;
+ }
+ // At this point some ancestor of aElement (possibly aElement
+ // itself) is in mRestyleRoots. Set the root bit on aElement, to
+ // speed up searching for an existing root on its descendants.
+ aElement->SetFlags(RootBit());
+ if (cur != aElement) {
+ // We are already going to restyle cur, one of aElement's ancestors,
+ // but we might not end up restyling all the way down to aElement.
+ // Record it in the RestyleData so we can ensure it does get restyled
+ // after we deal with cur.
+ //
+ // As with the mRestyleRoots array, mDescendants maintains the
+ // invariant that if two elements appear in the array and one
+ // is an ancestor of the other, that the ancestor appears after
+ // the descendant.
+ RestyleData* curData;
+ mPendingRestyles.Get(cur, &curData);
+ NS_ASSERTION(curData, "expected to find a RestyleData for cur");
+ // If cur has an eRestyle_ForceDescendants restyle hint, then we
+ // know that we will get to all descendants. Don't bother
+ // recording the descendant to restyle in that case.
+ if (curData && !(curData->mRestyleHint & eRestyle_ForceDescendants)) {
+ curData->mDescendants.AppendElement(aElement);
+ }
+ }
+ }
+
+ mHaveLaterSiblingRestyles =
+ mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
+ return hadRestyleLaterSiblings;
+}
+
+} // namespace mozilla
+
+#endif /* mozilla_RestyleTracker_h */
diff --git a/layout/base/RestyleTrackerInlines.h b/layout/base/RestyleTrackerInlines.h
new file mode 100644
index 000000000..b147afa2c
--- /dev/null
+++ b/layout/base/RestyleTrackerInlines.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RestyleTrackerInlines_h
+#define mozilla_RestyleTrackerInlines_h
+
+#ifdef RESTYLE_LOGGING
+bool
+mozilla::RestyleTracker::ShouldLogRestyle()
+{
+ return mRestyleManager->ShouldLogRestyle();
+}
+
+int32_t&
+mozilla::RestyleTracker::LoggingDepth()
+{
+ return mRestyleManager->LoggingDepth();
+}
+#endif
+
+#endif // !defined(mozilla_RestyleTrackerInlines_h)
diff --git a/layout/base/ScrollbarStyles.cpp b/layout/base/ScrollbarStyles.cpp
new file mode 100644
index 000000000..cc9f0c57d
--- /dev/null
+++ b/layout/base/ScrollbarStyles.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollbarStyles.h"
+#include "nsStyleStruct.h" // for nsStyleDisplay and nsStyleBackground::Position
+
+namespace mozilla {
+
+ ScrollbarStyles::ScrollbarStyles(uint8_t aH, uint8_t aV,
+ const nsStyleDisplay* aDisplay)
+ : mHorizontal(aH), mVertical(aV),
+ mScrollBehavior(aDisplay->mScrollBehavior),
+ mScrollSnapTypeX(aDisplay->mScrollSnapTypeX),
+ mScrollSnapTypeY(aDisplay->mScrollSnapTypeY),
+ mScrollSnapPointsX(aDisplay->mScrollSnapPointsX),
+ mScrollSnapPointsY(aDisplay->mScrollSnapPointsY),
+ mScrollSnapDestinationX(aDisplay->mScrollSnapDestination.mXPosition),
+ mScrollSnapDestinationY(aDisplay->mScrollSnapDestination.mYPosition) {}
+
+ ScrollbarStyles::ScrollbarStyles(const nsStyleDisplay* aDisplay)
+ : mHorizontal(aDisplay->mOverflowX), mVertical(aDisplay->mOverflowY),
+ mScrollBehavior(aDisplay->mScrollBehavior),
+ mScrollSnapTypeX(aDisplay->mScrollSnapTypeX),
+ mScrollSnapTypeY(aDisplay->mScrollSnapTypeY),
+ mScrollSnapPointsX(aDisplay->mScrollSnapPointsX),
+ mScrollSnapPointsY(aDisplay->mScrollSnapPointsY),
+ mScrollSnapDestinationX(aDisplay->mScrollSnapDestination.mXPosition),
+ mScrollSnapDestinationY(aDisplay->mScrollSnapDestination.mYPosition) {}
+
+} // namespace mozilla
diff --git a/layout/base/ScrollbarStyles.h b/layout/base/ScrollbarStyles.h
new file mode 100644
index 000000000..e6f0c6dde
--- /dev/null
+++ b/layout/base/ScrollbarStyles.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ScrollbarStyles_h
+#define ScrollbarStyles_h
+
+#include <stdint.h>
+#include "nsStyleConsts.h" // for NS_STYLE_SCROLL_SNAP_*
+#include "nsStyleCoord.h" // for nsStyleCoord
+#include "mozilla/dom/WindowBinding.h"
+
+// Forward declarations
+struct nsStyleDisplay;
+
+namespace mozilla {
+
+struct ScrollbarStyles
+{
+ // Always one of NS_STYLE_OVERFLOW_SCROLL, NS_STYLE_OVERFLOW_HIDDEN,
+ // or NS_STYLE_OVERFLOW_AUTO.
+ uint8_t mHorizontal;
+ uint8_t mVertical;
+ // Always one of NS_STYLE_SCROLL_BEHAVIOR_AUTO or
+ // NS_STYLE_SCROLL_BEHAVIOR_SMOOTH
+ uint8_t mScrollBehavior;
+ // Always one of NS_STYLE_SCROLL_SNAP_NONE, NS_STYLE_SCROLL_SNAP_MANDATORY,
+ // or NS_STYLE_SCROLL_SNAP_PROXIMITY.
+ uint8_t mScrollSnapTypeX;
+ uint8_t mScrollSnapTypeY;
+ nsStyleCoord mScrollSnapPointsX;
+ nsStyleCoord mScrollSnapPointsY;
+ nsStyleCoord::CalcValue mScrollSnapDestinationX;
+ nsStyleCoord::CalcValue mScrollSnapDestinationY;
+
+ ScrollbarStyles(uint8_t aH, uint8_t aV)
+ : mHorizontal(aH), mVertical(aV),
+ mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO),
+ mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE),
+ mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE),
+ mScrollSnapPointsX(nsStyleCoord(eStyleUnit_None)),
+ mScrollSnapPointsY(nsStyleCoord(eStyleUnit_None)) {
+
+ mScrollSnapDestinationX.mPercent = 0;
+ mScrollSnapDestinationX.mLength = nscoord(0.0f);
+ mScrollSnapDestinationX.mHasPercent = false;
+ mScrollSnapDestinationY.mPercent = 0;
+ mScrollSnapDestinationY.mLength = nscoord(0.0f);
+ mScrollSnapDestinationY.mHasPercent = false;
+ }
+
+ explicit ScrollbarStyles(const nsStyleDisplay* aDisplay);
+ ScrollbarStyles(uint8_t aH, uint8_t aV, const nsStyleDisplay* aDisplay);
+ ScrollbarStyles() {}
+ bool operator==(const ScrollbarStyles& aStyles) const {
+ return aStyles.mHorizontal == mHorizontal && aStyles.mVertical == mVertical &&
+ aStyles.mScrollBehavior == mScrollBehavior &&
+ aStyles.mScrollSnapTypeX == mScrollSnapTypeX &&
+ aStyles.mScrollSnapTypeY == mScrollSnapTypeY &&
+ aStyles.mScrollSnapPointsX == mScrollSnapPointsX &&
+ aStyles.mScrollSnapPointsY == mScrollSnapPointsY &&
+ aStyles.mScrollSnapDestinationX == mScrollSnapDestinationX &&
+ aStyles.mScrollSnapDestinationY == mScrollSnapDestinationY;
+ }
+ bool operator!=(const ScrollbarStyles& aStyles) const {
+ return !(*this == aStyles);
+ }
+ bool IsHiddenInBothDirections() const {
+ return mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
+ mVertical == NS_STYLE_OVERFLOW_HIDDEN;
+ }
+ bool IsSmoothScroll(dom::ScrollBehavior aBehavior) const {
+ return aBehavior == dom::ScrollBehavior::Smooth ||
+ (aBehavior == dom::ScrollBehavior::Auto &&
+ mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH);
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ServoRestyleManager.cpp b/layout/base/ServoRestyleManager.cpp
new file mode 100644
index 000000000..42ca23bb1
--- /dev/null
+++ b/layout/base/ServoRestyleManager.cpp
@@ -0,0 +1,594 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ServoRestyleManager.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "nsStyleChangeList.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
+ : RestyleManagerBase(aPresContext)
+{
+}
+
+void
+ServoRestyleManager::PostRestyleEvent(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint)
+{
+ if (MOZ_UNLIKELY(IsDisconnected()) ||
+ MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
+ return;
+ }
+
+ if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
+ return; // Nothing to do.
+ }
+
+ // XXX This is a temporary hack to make style attribute change works.
+ // In the future, we should be able to use this hint directly.
+ if (aRestyleHint & eRestyle_StyleAttribute) {
+ aRestyleHint &= ~eRestyle_StyleAttribute;
+ aRestyleHint |= eRestyle_Self | eRestyle_Subtree;
+ }
+
+ // Note that unlike in Servo, we don't mark elements as dirty until we process
+ // the restyle hints in ProcessPendingRestyles.
+ if (aRestyleHint || aMinChangeHint) {
+ ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+ snapshot->AddExplicitRestyleHint(aRestyleHint);
+ snapshot->AddExplicitChangeHint(aMinChangeHint);
+ }
+
+ PostRestyleEventInternal(false);
+}
+
+void
+ServoRestyleManager::PostRestyleEventForLazyConstruction()
+{
+ PostRestyleEventInternal(true);
+}
+
+void
+ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
+}
+
+void
+ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
+}
+
+static void
+MarkSelfAndDescendantsAsNotDirtyForServo(nsIContent* aContent)
+{
+ aContent->UnsetIsDirtyForServo();
+
+ if (aContent->HasDirtyDescendantsForServo()) {
+ aContent->UnsetHasDirtyDescendantsForServo();
+
+ StyleChildrenIterator it(aContent);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ MarkSelfAndDescendantsAsNotDirtyForServo(n);
+ }
+ }
+}
+
+void
+ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
+ nsStyleContext* aParentContext,
+ ServoStyleSet* aStyleSet,
+ nsStyleChangeList& aChangeListToProcess)
+{
+ MOZ_ASSERT(aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT));
+
+ nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
+ if (!primaryFrame && !aContent->IsDirtyForServo()) {
+ // This happens when, for example, a display: none child of a
+ // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
+ MarkSelfAndDescendantsAsNotDirtyForServo(aContent);
+ return;
+ }
+
+ // Work on text before.
+ if (!aContent->IsElement()) {
+ if (primaryFrame) {
+ RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
+ RefPtr<nsStyleContext> newContext =
+ aStyleSet->ResolveStyleForText(aContent, aParentContext);
+
+ for (nsIFrame* f = primaryFrame; f;
+ f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
+ f->SetStyleContext(newContext);
+ }
+ }
+
+ aContent->UnsetIsDirtyForServo();
+ return;
+ }
+
+ Element* element = aContent->AsElement();
+ if (element->IsDirtyForServo()) {
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_Get(aContent).Consume();
+ MOZ_ASSERT(computedValues);
+
+ nsChangeHint changeHint = nsChangeHint(0);
+
+ // Add an explicit change hint if appropriate.
+ ServoElementSnapshot* snapshot;
+ if (mModifiedElements.Get(element, &snapshot)) {
+ changeHint |= snapshot->ExplicitChangeHint();
+ }
+
+ // Add the stored change hint if there's a frame. If there isn't a frame,
+ // generate a ReconstructFrame change hint if the new display value
+ // (which we can get from the ComputedValues stored on the node) is not
+ // none.
+ if (primaryFrame) {
+ changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint();
+ } else {
+ const nsStyleDisplay* currentDisplay =
+ Servo_GetStyleDisplay(computedValues);
+ if (currentDisplay->mDisplay != StyleDisplay::None) {
+ changeHint |= nsChangeHint_ReconstructFrame;
+ }
+ }
+
+ // Add the new change hint to the list of elements to process if
+ // we need to do any work.
+ if (changeHint) {
+ aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
+ }
+
+ // The frame reconstruction step (if needed) will ask for the descendants'
+ // style correctly. If not needed, we're done too.
+ //
+ // Note that we must leave the old style on an existing frame that is
+ // about to be reframed, since some frame constructor code wants to
+ // inspect the old style to work out what to do.
+ if (changeHint & nsChangeHint_ReconstructFrame) {
+ // Since we might still have some dirty bits set on descendants,
+ // inconsistent with the clearing of HasDirtyDescendants we will do as
+ // we return from these recursive RecreateStyleContexts calls, we
+ // explicitly clear them here. Otherwise we will trigger assertions
+ // when we soon process the frame reconstruction.
+ MarkSelfAndDescendantsAsNotDirtyForServo(element);
+ return;
+ }
+
+ // If there is no frame, and we didn't generate a ReconstructFrame change
+ // hint, then we don't need to do any more work.
+ if (!primaryFrame) {
+ aContent->UnsetIsDirtyForServo();
+ return;
+ }
+
+ // Hold the old style context alive, because it could become a dangling
+ // pointer during the replacement. In practice it's not a huge deal (on
+ // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
+ // compared), but better not playing with dangling pointers if not needed.
+ RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
+ MOZ_ASSERT(oldStyleContext);
+
+ RefPtr<nsStyleContext> newContext =
+ aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
+ CSSPseudoElementType::NotPseudo);
+
+ // XXX This could not always work as expected: there are kinds of content
+ // with the first split and the last sharing style, but others not. We
+ // should handle those properly.
+ for (nsIFrame* f = primaryFrame; f;
+ f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
+ f->SetStyleContext(newContext);
+ }
+
+ // Update pseudo-elements state if appropriate.
+ const static CSSPseudoElementType pseudosToRestyle[] = {
+ CSSPseudoElementType::before,
+ CSSPseudoElementType::after,
+ };
+
+ for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
+ nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
+
+ if (nsIFrame* pseudoFrame = FrameForPseudoElement(element, pseudoTag)) {
+ // TODO: we could maybe make this more performant via calling into
+ // Servo just once to know which pseudo-elements we've got to restyle?
+ RefPtr<nsStyleContext> pseudoContext =
+ aStyleSet->ProbePseudoElementStyle(element, pseudoType, newContext);
+
+ // If pseudoContext is null here, it means the frame is going away, so
+ // our change hint computation should have already indicated we need
+ // to reframe.
+ MOZ_ASSERT_IF(!pseudoContext,
+ changeHint & nsChangeHint_ReconstructFrame);
+ if (pseudoContext) {
+ pseudoFrame->SetStyleContext(pseudoContext);
+
+ // We only care restyling text nodes, since other type of nodes
+ // (images), are still not supported. If that eventually changes, we
+ // may have to write more code here... Or not, I don't think too
+ // many inherited properties can affect those other frames.
+ StyleChildrenIterator it(pseudoFrame->GetContent());
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsNodeOfType(nsINode::eTEXT)) {
+ RefPtr<nsStyleContext> childContext =
+ aStyleSet->ResolveStyleForText(n, pseudoContext);
+ MOZ_ASSERT(n->GetPrimaryFrame(),
+ "How? This node is created at FC time!");
+ n->GetPrimaryFrame()->SetStyleContext(childContext);
+ }
+ }
+ }
+ }
+ }
+
+ aContent->UnsetIsDirtyForServo();
+ }
+
+ if (aContent->HasDirtyDescendantsForServo()) {
+ MOZ_ASSERT(primaryFrame,
+ "Frame construction should be scheduled, and it takes the "
+ "correct style for the children, so no need to be here.");
+ StyleChildrenIterator it(aContent);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsElement() || n->IsNodeOfType(nsINode::eTEXT)) {
+ RecreateStyleContexts(n, primaryFrame->StyleContext(),
+ aStyleSet, aChangeListToProcess);
+ }
+ }
+ aContent->UnsetHasDirtyDescendantsForServo();
+ }
+}
+
+static void
+MarkChildrenAsDirtyForServo(nsIContent* aContent)
+{
+ StyleChildrenIterator it(aContent);
+
+ nsIContent* n = it.GetNextChild();
+ bool hadChildren = bool(n);
+ for (; n; n = it.GetNextChild()) {
+ n->SetIsDirtyForServo();
+ }
+
+ if (hadChildren) {
+ aContent->SetHasDirtyDescendantsForServo();
+ }
+}
+
+/* static */ nsIFrame*
+ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
+ nsIAtom* aPseudoTagOrNull)
+{
+ MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
+ nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
+
+ if (!aPseudoTagOrNull) {
+ return primaryFrame;
+ }
+
+ if (!primaryFrame) {
+ return nullptr;
+ }
+
+ // NOTE: we probably need to special-case display: contents here. Gecko's
+ // RestyleManager passes the primary frame of the parent instead.
+ if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
+ return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
+ }
+
+ if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
+ return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
+ }
+
+ MOZ_CRASH("Unkown pseudo-element given to "
+ "ServoRestyleManager::FrameForPseudoElement");
+ return nullptr;
+}
+
+/* static */ void
+ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
+{
+ const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
+ eRestyle_Subtree |
+ eRestyle_LaterSiblings |
+ eRestyle_SomeDescendants;
+ // NB: For Servo, at least for now, restyling and running selector-matching
+ // against the subtree is necessary as part of restyling the element, so
+ // processing eRestyle_Self will perform at least as much work as
+ // eRestyle_Subtree.
+ if (aHint & (eRestyle_Self | eRestyle_Subtree)) {
+ aElement->SetIsDirtyForServo();
+ aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
+ // NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run
+ // selector matching on all the descendants. There's a bug on Servo to align
+ // meanings here (#12710) to avoid this potential source of confusion.
+ } else if (aHint & eRestyle_SomeDescendants) {
+ MarkChildrenAsDirtyForServo(aElement);
+ aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
+ }
+
+ if (aHint & eRestyle_LaterSiblings) {
+ aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
+ for (nsIContent* cur = aElement->GetNextSibling(); cur;
+ cur = cur->GetNextSibling()) {
+ cur->SetIsDirtyForServo();
+ }
+ }
+
+ // TODO: Handle all other nsRestyleHint values.
+ if (aHint & ~HANDLED_RESTYLE_HINTS) {
+ NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
+ RestyleManagerBase::RestyleHintToString(aHint).get()).get());
+ }
+}
+
+void
+ServoRestyleManager::ProcessPendingRestyles()
+{
+ MOZ_ASSERT(PresContext()->Document(), "No document? Pshaw!");
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
+
+ if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
+ // PresShell::FlushPendingNotifications doesn't early-return in the case
+ // where the PreShell hasn't yet been initialized (and therefore we haven't
+ // yet done the initial style traversal of the DOM tree). We should arguably
+ // fix up the callers and assert against this case, but we just detect and
+ // handle it for now.
+ return;
+ }
+
+ if (!HasPendingRestyles()) {
+ return;
+ }
+
+ ServoStyleSet* styleSet = StyleSet();
+ nsIDocument* doc = PresContext()->Document();
+ Element* root = doc->GetRootElement();
+ if (root) {
+ // ProcessPendingRestyles can generate new restyles (e.g. from the
+ // frame constructor if it decides that a ReconstructFrame change must
+ // apply to the parent of the element that generated that hint). So
+ // we loop while mModifiedElements still has some restyles in it, clearing
+ // it after each RecreateStyleContexts call below.
+ while (!mModifiedElements.IsEmpty()) {
+ for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
+ ServoElementSnapshot* snapshot = iter.UserData();
+ Element* element = iter.Key();
+
+ // The element is no longer in the document, so don't bother computing
+ // a final restyle hint for it.
+ //
+ // XXXheycam RestyleTracker checks that the element's GetComposedDoc()
+ // matches the document we're restyling. Do we need to do that too?
+ if (!element->IsInComposedDoc()) {
+ continue;
+ }
+
+ // TODO: avoid the ComputeRestyleHint call if we already have the highest
+ // explicit restyle hint?
+ nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
+ hint |= snapshot->ExplicitRestyleHint();
+
+ if (hint) {
+ NoteRestyleHint(element, hint);
+ }
+ }
+
+ if (!root->IsDirtyForServo() && !root->HasDirtyDescendantsForServo()) {
+ mModifiedElements.Clear();
+ break;
+ }
+
+ mInStyleRefresh = true;
+ styleSet->StyleDocument(/* aLeaveDirtyBits = */ true);
+
+ // First do any queued-up frame creation. (see bugs 827239 and 997506).
+ //
+ // XXXEmilio I'm calling this to avoid random behavior changes, since we
+ // delay frame construction after styling we should re-check once our
+ // model is more stable whether we can skip this call.
+ //
+ // Note this has to be *after* restyling, because otherwise frame
+ // construction will find unstyled nodes, and that's not funny.
+ PresContext()->FrameConstructor()->CreateNeededFrames();
+
+ nsStyleChangeList changeList;
+ RecreateStyleContexts(root, nullptr, styleSet, changeList);
+
+ mModifiedElements.Clear();
+ ProcessRestyledFrames(changeList);
+
+ mInStyleRefresh = false;
+ }
+ }
+
+ MOZ_ASSERT(!doc->IsDirtyForServo());
+ doc->UnsetHasDirtyDescendantsForServo();
+
+ IncrementRestyleGeneration();
+}
+
+void
+ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild)
+{
+ //
+ // XXXbholley: We need the Gecko logic here to correctly restyle for things
+ // like :empty and positional selectors (though we may not need to post
+ // restyle events as agressively as the Gecko path does).
+ //
+ // Bug 1297899 tracks this work.
+ //
+}
+
+void
+ServoRestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild)
+{
+ if (aContainer == aContainer->OwnerDoc()) {
+ // If we're getting this notification for the insertion of a root element,
+ // that means either:
+ // (a) We initialized the PresShell before the root element existed, or
+ // (b) The root element was removed and it or another root is being
+ // inserted.
+ //
+ // Either way the whole tree is dirty, so we should style the document.
+ MOZ_ASSERT(aChild == aChild->OwnerDoc()->GetRootElement());
+ MOZ_ASSERT(aChild->IsDirtyForServo());
+ StyleSet()->StyleDocument(/* aLeaveDirtyBits = */ false);
+ return;
+ }
+
+ if (!aContainer->HasServoData()) {
+ // This can happen with display:none. Bug 1297249 tracks more investigation
+ // and assertions here.
+ return;
+ }
+
+ // Style the new subtree because we will most likely need it during subsequent
+ // frame construction. Bug 1298281 tracks deferring this work in the lazy
+ // frame construction case.
+ StyleSet()->StyleNewSubtree(aChild);
+
+ RestyleForInsertOrChange(aContainer, aChild);
+}
+
+void
+ServoRestyleManager::RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ //
+ // XXXbholley: We need the Gecko logic here to correctly restyle for things
+ // like :empty and positional selectors (though we may not need to post
+ // restyle events as agressively as the Gecko path does).
+ //
+ // Bug 1297899 tracks this work.
+ //
+}
+
+void
+ServoRestyleManager::ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ if (!aContainer->HasServoData()) {
+ // This can happen with display:none. Bug 1297249 tracks more investigation
+ // and assertions here.
+ return;
+ }
+
+ // Style the new subtree because we will most likely need it during subsequent
+ // frame construction. Bug 1298281 tracks deferring this work in the lazy
+ // frame construction case.
+ if (aFirstNewContent->GetNextSibling()) {
+ aContainer->SetHasDirtyDescendantsForServo();
+ StyleSet()->StyleNewChildren(aContainer);
+ } else {
+ StyleSet()->StyleNewSubtree(aFirstNewContent);
+ }
+
+ RestyleForAppend(aContainer, aFirstNewContent);
+}
+
+void
+ServoRestyleManager::ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling)
+{
+ NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented");
+}
+
+nsresult
+ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
+ EventStates aChangedBits)
+{
+ if (!aContent->IsElement()) {
+ return NS_OK;
+ }
+
+ Element* aElement = aContent->AsElement();
+ nsChangeHint changeHint;
+ nsRestyleHint restyleHint;
+
+ // NOTE: restyleHint here is effectively always 0, since that's what
+ // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
+ // ProcessPendingRestyles using the ElementSnapshot, but in theory could
+ // compute it sequentially easily.
+ //
+ // Determine what's the best way to do it, and how much work do we save
+ // processing the restyle hint early (i.e., computing the style hint here
+ // sequentially, potentially saving the snapshot), vs lazily (snapshot
+ // approach).
+ //
+ // If we take the sequential approach we need to specialize Servo's restyle
+ // hints system a bit more, and mesure whether we save something storing the
+ // restyle hint in the table and deferring the dirtiness setting until
+ // ProcessPendingRestyles (that's a requirement if we store snapshots though),
+ // vs processing the restyle hint in-place, dirtying the nodes on
+ // PostRestyleEvent.
+ //
+ // If we definitely take the snapshot approach, we should take rid of
+ // HasStateDependentStyle, etc (though right now they're no-ops).
+ ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
+ &restyleHint);
+
+ EventStates previousState = aElement->StyleState() ^ aChangedBits;
+ ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+ snapshot->AddState(previousState);
+
+ PostRestyleEvent(aElement, restyleHint, changeHint);
+ return NS_OK;
+}
+
+void
+ServoRestyleManager::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+ snapshot->AddAttrs(aElement);
+}
+
+void
+ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs());
+ if (aAttribute == nsGkAtoms::style) {
+ PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
+ }
+}
+
+nsresult
+ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
+{
+ NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
+ return NS_OK;
+}
+
+ServoElementSnapshot*
+ServoRestyleManager::SnapshotForElement(Element* aElement)
+{
+ // NB: aElement is the argument for the construction of the snapshot in the
+ // not found case.
+ return mModifiedElements.LookupOrAdd(aElement, aElement);
+}
+
+} // namespace mozilla
diff --git a/layout/base/ServoRestyleManager.h b/layout/base/ServoRestyleManager.h
new file mode 100644
index 000000000..6856171c1
--- /dev/null
+++ b/layout/base/ServoRestyleManager.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoRestyleManager_h
+#define mozilla_ServoRestyleManager_h
+
+#include "mozilla/EventStates.h"
+#include "mozilla/RestyleManagerBase.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "nsChangeHint.h"
+#include "nsHashKeys.h"
+#include "nsINode.h"
+#include "nsISupportsImpl.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+class nsAttrValue;
+class nsIAtom;
+class nsIContent;
+class nsIFrame;
+class nsStyleChangeList;
+
+namespace mozilla {
+
+/**
+ * Restyle manager for a Servo-backed style system.
+ */
+class ServoRestyleManager : public RestyleManagerBase
+{
+ friend class ServoStyleSet;
+public:
+ typedef RestyleManagerBase base_type;
+
+ NS_INLINE_DECL_REFCOUNTING(ServoRestyleManager)
+
+ explicit ServoRestyleManager(nsPresContext* aPresContext);
+
+ void PostRestyleEvent(dom::Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint);
+ void PostRestyleEventForLazyConstruction();
+ void RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+ void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+ void ProcessPendingRestyles();
+
+ void ContentInserted(nsINode* aContainer, nsIContent* aChild);
+ void ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent);
+ void ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling);
+
+ void RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild);
+ void RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent);
+ nsresult ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask);
+ void AttributeWillChange(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue);
+
+ void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue);
+
+ nsresult ReparentStyleContext(nsIFrame* aFrame);
+
+ bool HasPendingRestyles()
+ {
+ return !mModifiedElements.IsEmpty() ||
+ PresContext()->Document()->HasDirtyDescendantsForServo();
+ }
+
+
+ /**
+ * Gets the appropriate frame given a content and a pseudo-element tag.
+ *
+ * Right now only supports a null tag, before or after. If the pseudo-element
+ * is not null, the content needs to be an element.
+ */
+ static nsIFrame* FrameForPseudoElement(const nsIContent* aContent,
+ nsIAtom* aPseudoTagOrNull);
+
+protected:
+ ~ServoRestyleManager() {}
+
+private:
+ ServoElementSnapshot* SnapshotForElement(Element* aElement);
+
+ /**
+ * The element-to-element snapshot table to compute restyle hints.
+ */
+ nsClassHashtable<nsRefPtrHashKey<Element>, ServoElementSnapshot>
+ mModifiedElements;
+
+ /**
+ * Traverses a tree of content that Servo has just restyled, recreating style
+ * contexts for their frames with the new style data.
+ */
+ void RecreateStyleContexts(nsIContent* aContent,
+ nsStyleContext* aParentContext,
+ ServoStyleSet* aStyleSet,
+ nsStyleChangeList& aChangeList);
+
+ /**
+ * Marks the tree with the appropriate dirty flags for the given restyle hint.
+ */
+ static void NoteRestyleHint(Element* aElement, nsRestyleHint aRestyleHint);
+
+ inline ServoStyleSet* StyleSet() const
+ {
+ MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
+ "ServoRestyleManager should only be used with a Servo-flavored "
+ "style backend");
+ return PresContext()->StyleSet()->AsServo();
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoRestyleManager_h
diff --git a/layout/base/StackArena.cpp b/layout/base/StackArena.cpp
new file mode 100644
index 000000000..1821b9721
--- /dev/null
+++ b/layout/base/StackArena.cpp
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StackArena.h"
+#include "nsAlgorithm.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+// A block of memory that the stack will chop up and hand out.
+struct StackBlock {
+ // Subtract sizeof(StackBlock*) to give space for the |mNext| field.
+ static const size_t MAX_USABLE_SIZE = 4096 - sizeof(StackBlock*);
+
+ // A block of memory.
+ char mBlock[MAX_USABLE_SIZE];
+
+ // Another block of memory that would only be created if our stack
+ // overflowed.
+ StackBlock* mNext;
+
+ StackBlock() : mNext(nullptr) { }
+ ~StackBlock() { }
+};
+
+static_assert(sizeof(StackBlock) == 4096, "StackBlock must be 4096 bytes");
+
+// We hold an array of marks. A push pushes a mark on the stack.
+// A pop pops it off.
+struct StackMark {
+ // The block of memory from which we are currently handing out chunks.
+ StackBlock* mBlock;
+
+ // Our current position in the block.
+ size_t mPos;
+};
+
+StackArena* AutoStackArena::gStackArena;
+
+StackArena::StackArena()
+{
+ mMarkLength = 0;
+ mMarks = nullptr;
+
+ // Allocate our stack memory.
+ mBlocks = new StackBlock();
+ mCurBlock = mBlocks;
+
+ mStackTop = 0;
+ mPos = 0;
+}
+
+StackArena::~StackArena()
+{
+ // Free up our data.
+ delete [] mMarks;
+ while (mBlocks) {
+ StackBlock* toDelete = mBlocks;
+ mBlocks = mBlocks->mNext;
+ delete toDelete;
+ }
+}
+
+size_t
+StackArena::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ StackBlock *block = mBlocks;
+ while (block) {
+ n += aMallocSizeOf(block);
+ block = block->mNext;
+ }
+ n += aMallocSizeOf(mMarks);
+ return n;
+}
+
+static const int STACK_ARENA_MARK_INCREMENT = 50;
+
+void
+StackArena::Push()
+{
+ // Resize the mark array if we overrun it. Failure to allocate the
+ // mark array is not fatal; we just won't free to that mark. This
+ // allows callers not to worry about error checking.
+ if (mStackTop >= mMarkLength) {
+ uint32_t newLength = mStackTop + STACK_ARENA_MARK_INCREMENT;
+ StackMark* newMarks = new StackMark[newLength];
+ if (newMarks) {
+ if (mMarkLength) {
+ memcpy(newMarks, mMarks, sizeof(StackMark)*mMarkLength);
+ }
+ // Fill in any marks that we couldn't allocate during a prior call
+ // to Push().
+ for (; mMarkLength < mStackTop; ++mMarkLength) {
+ NS_NOTREACHED("should only hit this on out-of-memory");
+ newMarks[mMarkLength].mBlock = mCurBlock;
+ newMarks[mMarkLength].mPos = mPos;
+ }
+ delete [] mMarks;
+ mMarks = newMarks;
+ mMarkLength = newLength;
+ }
+ }
+
+ // Set a mark at the top (if we can).
+ NS_ASSERTION(mStackTop < mMarkLength, "out of memory");
+ if (mStackTop < mMarkLength) {
+ mMarks[mStackTop].mBlock = mCurBlock;
+ mMarks[mStackTop].mPos = mPos;
+ }
+
+ mStackTop++;
+}
+
+void*
+StackArena::Allocate(size_t aSize)
+{
+ NS_ASSERTION(mStackTop > 0, "Allocate called without Push");
+
+ // Align to a multiple of 8.
+ aSize = NS_ROUNDUP<size_t>(aSize, 8);
+
+ // On stack overflow, grab another block.
+ if (mPos + aSize >= StackBlock::MAX_USABLE_SIZE) {
+ NS_ASSERTION(aSize <= StackBlock::MAX_USABLE_SIZE,
+ "Requested memory is greater that our block size!!");
+ if (mCurBlock->mNext == nullptr) {
+ mCurBlock->mNext = new StackBlock();
+ }
+
+ mCurBlock = mCurBlock->mNext;
+ mPos = 0;
+ }
+
+ // Return the chunk they need.
+ void *result = mCurBlock->mBlock + mPos;
+ mPos += aSize;
+
+ return result;
+}
+
+void
+StackArena::Pop()
+{
+ // Pop off the mark.
+ NS_ASSERTION(mStackTop > 0, "unmatched pop");
+ mStackTop--;
+
+ if (mStackTop >= mMarkLength) {
+ // We couldn't allocate the marks array at the time of the push, so
+ // we don't know where we're freeing to.
+ NS_NOTREACHED("out of memory");
+ if (mStackTop == 0) {
+ // But we do know if we've completely pushed the stack.
+ mCurBlock = mBlocks;
+ mPos = 0;
+ }
+ return;
+ }
+
+#ifdef DEBUG
+ // Mark the "freed" memory with 0xdd to help with debugging of memory
+ // allocation problems.
+ {
+ StackBlock *block = mMarks[mStackTop].mBlock, *block_end = mCurBlock;
+ size_t pos = mMarks[mStackTop].mPos;
+ for (; block != block_end; block = block->mNext, pos = 0) {
+ memset(block->mBlock + pos, 0xdd, sizeof(block->mBlock) - pos);
+ }
+ memset(block->mBlock + pos, 0xdd, mPos - pos);
+ }
+#endif
+
+ mCurBlock = mMarks[mStackTop].mBlock;
+ mPos = mMarks[mStackTop].mPos;
+}
+
+} // namespace mozilla
diff --git a/layout/base/StackArena.h b/layout/base/StackArena.h
new file mode 100644
index 000000000..fdfe943ae
--- /dev/null
+++ b/layout/base/StackArena.h
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef StackArena_h
+#define StackArena_h
+
+#include "nsError.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+struct StackBlock;
+struct StackMark;
+class AutoStackArena;
+
+// Private helper class for AutoStackArena.
+class StackArena {
+private:
+ friend class AutoStackArena;
+ StackArena();
+ ~StackArena();
+
+ // Memory management functions.
+ void* Allocate(size_t aSize);
+ void Push();
+ void Pop();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Our current position in memory.
+ size_t mPos;
+
+ // A list of memory blocks. Usually there is only one
+ // but if we overrun our stack size we can get more memory.
+ StackBlock* mBlocks;
+
+ // The current block.
+ StackBlock* mCurBlock;
+
+ // Our stack of mark where push has been called.
+ StackMark* mMarks;
+
+ // The current top of the mark list.
+ uint32_t mStackTop;
+
+ // The size of the mark array.
+ uint32_t mMarkLength;
+};
+
+// Class for stack scoped arena memory allocations.
+//
+// Callers who wish to allocate memory whose lifetime corresponds to the
+// lifetime of a stack-allocated object can use this class. First,
+// declare an AutoStackArena object on the stack. Then all subsequent
+// calls to Allocate will allocate memory from an arena pool that will
+// be freed when that variable goes out of scope. Nesting is allowed.
+//
+// Individual allocations cannot exceed StackBlock::MAX_USABLE_SIZE
+// bytes.
+//
+class MOZ_RAII AutoStackArena {
+public:
+ AutoStackArena()
+ : mOwnsStackArena(false)
+ {
+ if (!gStackArena) {
+ gStackArena = new StackArena();
+ mOwnsStackArena = true;
+ }
+ gStackArena->Push();
+ }
+
+ ~AutoStackArena() {
+ gStackArena->Pop();
+ if (mOwnsStackArena) {
+ delete gStackArena;
+ gStackArena = nullptr;
+ }
+ }
+
+ static void* Allocate(size_t aSize) {
+ return gStackArena->Allocate(aSize);
+ }
+
+private:
+ static StackArena* gStackArena;
+ bool mOwnsStackArena;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/StaticPresData.cpp b/layout/base/StaticPresData.cpp
new file mode 100644
index 000000000..73acd5440
--- /dev/null
+++ b/layout/base/StaticPresData.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/StaticPresData.h"
+
+#include "mozilla/Preferences.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+static StaticPresData* sSingleton = nullptr;
+
+void
+StaticPresData::Init()
+{
+ MOZ_ASSERT(!sSingleton);
+ sSingleton = new StaticPresData();
+}
+
+void
+StaticPresData::Shutdown()
+{
+ MOZ_ASSERT(sSingleton);
+ delete sSingleton;
+ sSingleton = nullptr;
+}
+
+StaticPresData*
+StaticPresData::Get()
+{
+ MOZ_ASSERT(sSingleton);
+ return sSingleton;
+}
+
+StaticPresData::StaticPresData()
+{
+ mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
+
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THIN] = nsPresContext::CSSPixelsToAppUnits(1);
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_MEDIUM] = nsPresContext::CSSPixelsToAppUnits(3);
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THICK] = nsPresContext::CSSPixelsToAppUnits(5);
+}
+
+#define MAKE_FONT_PREF_KEY(_pref, _s0, _s1) \
+ _pref.Assign(_s0); \
+ _pref.Append(_s1);
+
+static const char* const kGenericFont[] = {
+ ".variable.",
+ ".fixed.",
+ ".serif.",
+ ".sans-serif.",
+ ".monospace.",
+ ".cursive.",
+ ".fantasy."
+};
+
+// These are private, use the list in nsFont.h if you want a public list.
+enum {
+ eDefaultFont_Variable,
+ eDefaultFont_Fixed,
+ eDefaultFont_Serif,
+ eDefaultFont_SansSerif,
+ eDefaultFont_Monospace,
+ eDefaultFont_Cursive,
+ eDefaultFont_Fantasy,
+ eDefaultFont_COUNT
+};
+
+const LangGroupFontPrefs*
+StaticPresData::GetFontPrefsForLangHelper(nsIAtom *aLanguage,
+ const LangGroupFontPrefs* aPrefs) const
+{
+ // Get language group for aLanguage:
+ MOZ_ASSERT(aLanguage);
+ MOZ_ASSERT(mLangService);
+ MOZ_ASSERT(aPrefs);
+
+ nsresult rv = NS_OK;
+ nsIAtom *langGroupAtom = nullptr;
+ langGroupAtom = mLangService->GetLanguageGroup(aLanguage, &rv);
+ if (NS_FAILED(rv) || !langGroupAtom) {
+ langGroupAtom = nsGkAtoms::x_western; // Assume x-western is safe...
+ }
+
+ LangGroupFontPrefs *prefs = const_cast<LangGroupFontPrefs*>(aPrefs);
+ if (prefs->mLangGroup) { // if initialized
+ DebugOnly<uint32_t> count = 0;
+ for (;;) {
+ NS_ASSERTION(++count < 35, "Lang group count exceeded!!!");
+ if (prefs->mLangGroup == langGroupAtom) {
+ return prefs;
+ }
+ if (!prefs->mNext) {
+ break;
+ }
+ prefs = prefs->mNext;
+ }
+
+ // nothing cached, so go on and fetch the prefs for this lang group:
+ prefs = prefs->mNext = new LangGroupFontPrefs;
+ }
+
+ prefs->mLangGroup = langGroupAtom;
+
+ /* Fetch the font prefs to be used -- see bug 61883 for details.
+ Not all prefs are needed upfront. Some are fallback prefs intended
+ for the GFX font sub-system...
+
+ 1) unit : assumed to be the same for all language groups -------------
+ font.size.unit = px | pt XXX could be folded in the size... bug 90440
+
+ 2) attributes for generic fonts --------------------------------------
+ font.default.[langGroup] = serif | sans-serif - fallback generic font
+ font.name.[generic].[langGroup] = current user' selected font on the pref dialog
+ font.name-list.[generic].[langGroup] = fontname1, fontname2, ... [factory pre-built list]
+ font.size.[generic].[langGroup] = integer - settable by the user
+ font.size-adjust.[generic].[langGroup] = "float" - settable by the user
+ font.minimum-size.[langGroup] = integer - settable by the user
+ */
+
+ nsAutoCString langGroup;
+ langGroupAtom->ToUTF8String(langGroup);
+
+ prefs->mDefaultVariableFont.size = nsPresContext::CSSPixelsToAppUnits(16);
+ prefs->mDefaultFixedFont.size = nsPresContext::CSSPixelsToAppUnits(13);
+
+ nsAutoCString pref;
+
+ // get the current applicable font-size unit
+ enum {eUnit_unknown = -1, eUnit_px, eUnit_pt};
+ int32_t unit = eUnit_px;
+
+ nsAdoptingCString cvalue =
+ Preferences::GetCString("font.size.unit");
+
+ if (!cvalue.IsEmpty()) {
+ if (cvalue.EqualsLiteral("px")) {
+ unit = eUnit_px;
+ }
+ else if (cvalue.EqualsLiteral("pt")) {
+ unit = eUnit_pt;
+ }
+ else {
+ // XXX should really send this warning to the user (Error Console?).
+ // And just default to unit = eUnit_px?
+ NS_WARNING("unexpected font-size unit -- expected: 'px' or 'pt'");
+ unit = eUnit_unknown;
+ }
+ }
+
+ // get font.minimum-size.[langGroup]
+
+ MAKE_FONT_PREF_KEY(pref, "font.minimum-size.", langGroup);
+
+ int32_t size = Preferences::GetInt(pref.get());
+ if (unit == eUnit_px) {
+ prefs->mMinimumFontSize = nsPresContext::CSSPixelsToAppUnits(size);
+ }
+ else if (unit == eUnit_pt) {
+ prefs->mMinimumFontSize = nsPresContext::CSSPointsToAppUnits(size);
+ }
+
+ nsFont* fontTypes[] = {
+ &prefs->mDefaultVariableFont,
+ &prefs->mDefaultFixedFont,
+ &prefs->mDefaultSerifFont,
+ &prefs->mDefaultSansSerifFont,
+ &prefs->mDefaultMonospaceFont,
+ &prefs->mDefaultCursiveFont,
+ &prefs->mDefaultFantasyFont
+ };
+ static_assert(MOZ_ARRAY_LENGTH(fontTypes) == eDefaultFont_COUNT,
+ "FontTypes array count is not correct");
+
+ // Get attributes specific to each generic font. We do not get the user's
+ // generic-font-name-to-specific-family-name preferences because its the
+ // generic name that should be fed into the cascade. It is up to the GFX
+ // code to look up the font prefs to convert generic names to specific
+ // family names as necessary.
+ nsAutoCString generic_dot_langGroup;
+ for (uint32_t eType = 0; eType < ArrayLength(fontTypes); ++eType) {
+ generic_dot_langGroup.Assign(kGenericFont[eType]);
+ generic_dot_langGroup.Append(langGroup);
+
+ nsFont* font = fontTypes[eType];
+
+ // set the default variable font (the other fonts are seen as 'generic' fonts
+ // in GFX and will be queried there when hunting for alternative fonts)
+ if (eType == eDefaultFont_Variable) {
+ MAKE_FONT_PREF_KEY(pref, "font.name.variable.", langGroup);
+
+ nsAdoptingString value = Preferences::GetString(pref.get());
+ if (!value.IsEmpty()) {
+ FontFamilyName defaultVariableName = FontFamilyName::Convert(value);
+ FontFamilyType defaultType = defaultVariableName.mType;
+ NS_ASSERTION(defaultType == eFamily_serif ||
+ defaultType == eFamily_sans_serif,
+ "default type must be serif or sans-serif");
+ prefs->mDefaultVariableFont.fontlist = FontFamilyList(defaultType);
+ }
+ else {
+ MAKE_FONT_PREF_KEY(pref, "font.default.", langGroup);
+ value = Preferences::GetString(pref.get());
+ if (!value.IsEmpty()) {
+ FontFamilyName defaultVariableName = FontFamilyName::Convert(value);
+ FontFamilyType defaultType = defaultVariableName.mType;
+ NS_ASSERTION(defaultType == eFamily_serif ||
+ defaultType == eFamily_sans_serif,
+ "default type must be serif or sans-serif");
+ prefs->mDefaultVariableFont.fontlist = FontFamilyList(defaultType);
+ }
+ }
+ }
+ else {
+ if (eType == eDefaultFont_Monospace) {
+ // This takes care of the confusion whereby people often expect "monospace"
+ // to have the same default font-size as "-moz-fixed" (this tentative
+ // size may be overwritten with the specific value for "monospace" when
+ // "font.size.monospace.[langGroup]" is read -- see below)
+ prefs->mDefaultMonospaceFont.size = prefs->mDefaultFixedFont.size;
+ }
+ else if (eType != eDefaultFont_Fixed) {
+ // all the other generic fonts are initialized with the size of the
+ // variable font, but their specific size can supersede later -- see below
+ font->size = prefs->mDefaultVariableFont.size;
+ }
+ }
+
+ // Bug 84398: for spec purists, a different font-size only applies to the
+ // .variable. and .fixed. fonts and the other fonts should get |font-size-adjust|.
+ // The problem is that only GfxWin has the support for |font-size-adjust|. So for
+ // parity, we enable the ability to set a different font-size on all platforms.
+
+ // get font.size.[generic].[langGroup]
+ // size=0 means 'Auto', i.e., generic fonts retain the size of the variable font
+ MAKE_FONT_PREF_KEY(pref, "font.size", generic_dot_langGroup);
+ size = Preferences::GetInt(pref.get());
+ if (size > 0) {
+ if (unit == eUnit_px) {
+ font->size = nsPresContext::CSSPixelsToAppUnits(size);
+ }
+ else if (unit == eUnit_pt) {
+ font->size = nsPresContext::CSSPointsToAppUnits(size);
+ }
+ }
+
+ // get font.size-adjust.[generic].[langGroup]
+ // XXX only applicable on GFX ports that handle |font-size-adjust|
+ MAKE_FONT_PREF_KEY(pref, "font.size-adjust", generic_dot_langGroup);
+ cvalue = Preferences::GetCString(pref.get());
+ if (!cvalue.IsEmpty()) {
+ font->sizeAdjust = (float)atof(cvalue.get());
+ }
+
+#ifdef DEBUG_rbs
+ printf("%s Family-list:%s size:%d sizeAdjust:%.2f\n",
+ generic_dot_langGroup.get(),
+ NS_ConvertUTF16toUTF8(font->name).get(), font->size,
+ font->sizeAdjust);
+#endif
+ }
+
+ return prefs;
+}
+
+const nsFont*
+StaticPresData::GetDefaultFontHelper(uint8_t aFontID, nsIAtom *aLanguage,
+ const LangGroupFontPrefs* aPrefs) const
+{
+ MOZ_ASSERT(aLanguage);
+ MOZ_ASSERT(aPrefs);
+
+ const nsFont *font;
+ switch (aFontID) {
+ // Special (our default variable width font and fixed width font)
+ case kPresContext_DefaultVariableFont_ID:
+ font = &aPrefs->mDefaultVariableFont;
+ break;
+ case kPresContext_DefaultFixedFont_ID:
+ font = &aPrefs->mDefaultFixedFont;
+ break;
+ // CSS
+ case kGenericFont_serif:
+ font = &aPrefs->mDefaultSerifFont;
+ break;
+ case kGenericFont_sans_serif:
+ font = &aPrefs->mDefaultSansSerifFont;
+ break;
+ case kGenericFont_monospace:
+ font = &aPrefs->mDefaultMonospaceFont;
+ break;
+ case kGenericFont_cursive:
+ font = &aPrefs->mDefaultCursiveFont;
+ break;
+ case kGenericFont_fantasy:
+ font = &aPrefs->mDefaultFantasyFont;
+ break;
+ default:
+ font = nullptr;
+ NS_ERROR("invalid arg");
+ break;
+ }
+ return font;
+}
+
+
+} // namespace mozilla
diff --git a/layout/base/StaticPresData.h b/layout/base/StaticPresData.h
new file mode 100644
index 000000000..62e174fd2
--- /dev/null
+++ b/layout/base/StaticPresData.h
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StaticPresData_h
+#define mozilla_StaticPresData_h
+
+#include "nsAutoPtr.h"
+#include "nsCoord.h"
+#include "nsCOMPtr.h"
+#include "nsFont.h"
+#include "nsIAtom.h"
+#include "nsILanguageAtomService.h"
+
+namespace mozilla {
+
+struct LangGroupFontPrefs {
+ // Font sizes default to zero; they will be set in GetFontPreferences
+ LangGroupFontPrefs()
+ : mLangGroup(nullptr)
+ , mMinimumFontSize(0)
+ , mDefaultVariableFont(mozilla::eFamily_serif, 0)
+ , mDefaultFixedFont(mozilla::eFamily_monospace, 0)
+ , mDefaultSerifFont(mozilla::eFamily_serif, 0)
+ , mDefaultSansSerifFont(mozilla::eFamily_sans_serif, 0)
+ , mDefaultMonospaceFont(mozilla::eFamily_monospace, 0)
+ , mDefaultCursiveFont(mozilla::eFamily_cursive, 0)
+ , mDefaultFantasyFont(mozilla::eFamily_fantasy, 0)
+ {}
+
+ void Reset()
+ {
+ // Throw away any other LangGroupFontPrefs objects:
+ mNext = nullptr;
+
+ // Make GetFontPreferences reinitialize mLangGroupFontPrefs:
+ mLangGroup = nullptr;
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ LangGroupFontPrefs* curr = mNext;
+ while (curr) {
+ n += aMallocSizeOf(curr);
+
+ // Measurement of the following members may be added later if DMD finds
+ // it is worthwhile:
+ // - mLangGroup
+ // - mDefault*Font
+
+ curr = curr->mNext;
+ }
+ return n;
+ }
+
+ nsCOMPtr<nsIAtom> mLangGroup;
+ nscoord mMinimumFontSize;
+ nsFont mDefaultVariableFont;
+ nsFont mDefaultFixedFont;
+ nsFont mDefaultSerifFont;
+ nsFont mDefaultSansSerifFont;
+ nsFont mDefaultMonospaceFont;
+ nsFont mDefaultCursiveFont;
+ nsFont mDefaultFantasyFont;
+ nsAutoPtr<LangGroupFontPrefs> mNext;
+};
+
+/**
+ * Some functionality that has historically lived on nsPresContext does not
+ * actually need to be per-document. This singleton class serves as a host
+ * for that functionality. We delegate to it from nsPresContext where
+ * appropriate, and use it standalone in some cases as well.
+ */
+class StaticPresData
+{
+public:
+ // Initialization and shutdown of the singleton. Called exactly once.
+ static void Init();
+ static void Shutdown();
+
+ // Gets an instance of the singleton. Infallible between the calls to Init
+ // and Shutdown.
+ static StaticPresData* Get();
+
+ /**
+ * This table maps border-width enums 'thin', 'medium', 'thick'
+ * to actual nscoord values.
+ */
+ const nscoord* GetBorderWidthTable() { return mBorderWidthTable; }
+
+ /**
+ * Fetch the user's font preferences for the given aLanguage's
+ * langugage group.
+ *
+ * The original code here is pretty old, and includes an optimization
+ * whereby language-specific prefs are read per-document, and the
+ * results are stored in a linked list, which is assumed to be very short
+ * since most documents only ever use one language.
+ *
+ * Storing this per-session rather than per-document would almost certainly
+ * be fine. But just to be on the safe side, we leave the old mechanism as-is,
+ * with an additional per-session cache that new callers can use if they don't
+ * have a PresContext.
+ */
+ const LangGroupFontPrefs* GetFontPrefsForLangHelper(nsIAtom* aLanguage,
+ const LangGroupFontPrefs* aPrefs) const;
+ /**
+ * Get the default font for the given language and generic font ID.
+ * aLanguage may not be nullptr.
+ *
+ * This object is read-only, you must copy the font to modify it.
+ *
+ * When aFontID is kPresContext_DefaultVariableFontID or
+ * kPresContext_DefaultFixedFontID (which equals
+ * kGenericFont_moz_fixed, which is used for the -moz-fixed generic),
+ * the nsFont returned has its name as a CSS generic family (serif or
+ * sans-serif for the former, monospace for the latter), and its size
+ * as the default font size for variable or fixed fonts for the
+ * language group.
+ *
+ * For aFontID corresponding to a CSS Generic, the nsFont returned has
+ * its name set to that generic font's name, and its size set to
+ * the user's preference for font size for that generic and the
+ * given language.
+ */
+ const nsFont* GetDefaultFontHelper(uint8_t aFontID,
+ nsIAtom* aLanguage,
+ const LangGroupFontPrefs* aPrefs) const;
+
+ /*
+ * These versions operate on the font pref cache on StaticPresData.
+ */
+
+ const nsFont* GetDefaultFont(uint8_t aFontID, nsIAtom* aLanguage) const
+ {
+ MOZ_ASSERT(aLanguage);
+ return GetDefaultFontHelper(aFontID, aLanguage, GetFontPrefsForLang(aLanguage));
+ }
+ const LangGroupFontPrefs* GetFontPrefsForLang(nsIAtom* aLanguage) const
+ {
+ MOZ_ASSERT(aLanguage);
+ return GetFontPrefsForLangHelper(aLanguage, &mStaticLangGroupFontPrefs);
+ }
+
+ void ResetCachedFontPrefs() { mStaticLangGroupFontPrefs.Reset(); }
+
+private:
+ StaticPresData();
+ ~StaticPresData() {}
+
+ nsCOMPtr<nsILanguageAtomService> mLangService;
+ nscoord mBorderWidthTable[3];
+ LangGroupFontPrefs mStaticLangGroupFontPrefs;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StaticPresData_h
diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp
new file mode 100644
index 000000000..5167ca588
--- /dev/null
+++ b/layout/base/TouchManager.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+#include "TouchManager.h"
+
+#include "mozilla/dom/EventTarget.h"
+#include "nsIFrame.h"
+#include "nsPresShell.h"
+#include "nsView.h"
+
+namespace mozilla {
+
+nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>* TouchManager::sCaptureTouchList;
+
+/*static*/ void
+TouchManager::InitializeStatics()
+{
+ NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
+ sCaptureTouchList = new nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>;
+}
+
+/*static*/ void
+TouchManager::ReleaseStatics()
+{
+ NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
+ delete sCaptureTouchList;
+ sCaptureTouchList = nullptr;
+}
+
+void
+TouchManager::Init(PresShell* aPresShell, nsIDocument* aDocument)
+{
+ mPresShell = aPresShell;
+ mDocument = aDocument;
+}
+
+void
+TouchManager::Destroy()
+{
+ EvictTouches();
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+static nsIContent*
+GetNonAnonymousAncestor(dom::EventTarget* aTarget)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
+ if (content && content->IsInNativeAnonymousSubtree()) {
+ content = content->FindFirstNonChromeOnlyAccessContent();
+ }
+ return content;
+}
+
+/*static*/ void
+TouchManager::EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
+ nsIDocument* aLimitToDocument)
+{
+ nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mTarget));
+ if (node) {
+ nsIDocument* doc = node->GetUncomposedDoc();
+ if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (frame) {
+ nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
+ nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
+ if (widget) {
+ WidgetTouchEvent event(true, eTouchEnd, widget);
+ event.mTime = PR_IntervalNow();
+ event.mTouches.AppendElement(aTouch);
+ nsEventStatus status;
+ widget->DispatchEvent(&event, status);
+ }
+ }
+ }
+ }
+ }
+ if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
+ sCaptureTouchList->Remove(aTouch->Identifier());
+ }
+}
+
+/*static*/ void
+TouchManager::AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList)
+{
+ for (auto iter = sCaptureTouchList->Iter();
+ !iter.Done();
+ iter.Next()) {
+ RefPtr<dom::Touch>& touch = iter.Data().mTouch;
+ touch->mChanged = false;
+ aTouchList->AppendElement(touch);
+ }
+}
+
+void
+TouchManager::EvictTouches()
+{
+ WidgetTouchEvent::AutoTouchArray touches;
+ AppendToTouchList(&touches);
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ EvictTouchPoint(touches[i], mDocument);
+ }
+}
+
+bool
+TouchManager::PreHandleEvent(WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ bool& aTouchIsNew,
+ bool& aIsHandlingUserInput,
+ nsCOMPtr<nsIContent>& aCurrentEventContent)
+{
+ switch (aEvent->mMessage) {
+ case eTouchStart: {
+ aIsHandlingUserInput = true;
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ // if there is only one touch in this touchstart event, assume that it is
+ // the start of a new touch session and evict any old touches in the
+ // queue
+ if (touchEvent->mTouches.Length() == 1) {
+ WidgetTouchEvent::AutoTouchArray touches;
+ AppendToTouchList(&touches);
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ EvictTouchPoint(touches[i]);
+ }
+ }
+ // Add any new touches to the queue
+ for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
+ dom::Touch* touch = touchEvent->mTouches[i];
+ int32_t id = touch->Identifier();
+ if (!sCaptureTouchList->Get(id, nullptr)) {
+ // If it is not already in the queue, it is a new touch
+ touch->mChanged = true;
+ }
+ touch->mMessage = aEvent->mMessage;
+ TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget) };
+ sCaptureTouchList->Put(id, info);
+ }
+ break;
+ }
+ case eTouchMove: {
+ // Check for touches that changed. Mark them add to queue
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ bool haveChanged = false;
+ for (int32_t i = touches.Length(); i; ) {
+ --i;
+ dom::Touch* touch = touches[i];
+ if (!touch) {
+ continue;
+ }
+ int32_t id = touch->Identifier();
+ touch->mMessage = aEvent->mMessage;
+
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(id, &info)) {
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ RefPtr<dom::Touch> oldTouch = info.mTouch;
+ if (!touch->Equals(oldTouch)) {
+ touch->mChanged = true;
+ haveChanged = true;
+ }
+
+ nsCOMPtr<dom::EventTarget> targetPtr = oldTouch->mTarget;
+ if (!targetPtr) {
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
+ if (!targetNode->IsInComposedDoc()) {
+ targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
+ }
+ touch->SetTarget(targetPtr);
+
+ info.mTouch = touch;
+ // info.mNonAnonymousTarget is still valid from above
+ sCaptureTouchList->Put(id, info);
+ // if we're moving from touchstart to touchmove for this touch
+ // we allow preventDefault to prevent mouse events
+ if (oldTouch->mMessage != touch->mMessage) {
+ aTouchIsNew = true;
+ }
+ }
+ // is nothing has changed, we should just return
+ if (!haveChanged) {
+ if (aTouchIsNew) {
+ // however, if this is the first touchmove after a touchstart,
+ // it is special in that preventDefault is allowed on it, so
+ // we must dispatch it to content even if nothing changed. we
+ // arbitrarily pick the first touch point to be the "changed"
+ // touch because firing an event with no changed events doesn't
+ // work.
+ for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
+ if (touchEvent->mTouches[i]) {
+ touchEvent->mTouches[i]->mChanged = true;
+ break;
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+ break;
+ }
+ case eTouchEnd:
+ aIsHandlingUserInput = true;
+ // Fall through to touchcancel code
+ MOZ_FALLTHROUGH;
+ case eTouchCancel: {
+ // Remove the changed touches
+ // need to make sure we only remove touches that are ending here
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ dom::Touch* touch = touches[i];
+ if (!touch) {
+ continue;
+ }
+ touch->mMessage = aEvent->mMessage;
+ touch->mChanged = true;
+
+ int32_t id = touch->Identifier();
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(id, &info)) {
+ continue;
+ }
+ nsCOMPtr<EventTarget> targetPtr = info.mTouch->mTarget;
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
+ if (targetNode && !targetNode->IsInComposedDoc()) {
+ targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
+ }
+
+ aCurrentEventContent = do_QueryInterface(targetPtr);
+ touch->SetTarget(targetPtr);
+ sCaptureTouchList->Remove(id);
+ }
+ // add any touches left in the touch list, but ensure changed=false
+ AppendToTouchList(&touches);
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+
+/*static*/ already_AddRefed<nsIContent>
+TouchManager::GetAnyCapturedTouchTarget()
+{
+ nsCOMPtr<nsIContent> result = nullptr;
+ if (sCaptureTouchList->Count() == 0) {
+ return result.forget();
+ }
+ for (auto iter = sCaptureTouchList->Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<dom::Touch>& touch = iter.Data().mTouch;
+ if (touch) {
+ dom::EventTarget* target = touch->GetTarget();
+ if (target) {
+ result = do_QueryInterface(target);
+ break;
+ }
+ }
+ }
+ return result.forget();
+}
+
+/*static*/ bool
+TouchManager::HasCapturedTouch(int32_t aId)
+{
+ return sCaptureTouchList->Contains(aId);
+}
+
+/*static*/ already_AddRefed<dom::Touch>
+TouchManager::GetCapturedTouch(int32_t aId)
+{
+ RefPtr<dom::Touch> touch;
+ TouchInfo info;
+ if (sCaptureTouchList->Get(aId, &info)) {
+ touch = info.mTouch;
+ }
+ return touch.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/base/TouchManager.h b/layout/base/TouchManager.h
new file mode 100644
index 000000000..7c0d51734
--- /dev/null
+++ b/layout/base/TouchManager.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/* Description of TouchManager class.
+ * Incapsulate code related with work of touch events.
+ */
+
+#ifndef TouchManager_h_
+#define TouchManager_h_
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/TouchEvents.h"
+#include "nsRefPtrHashtable.h"
+
+class PresShell;
+class nsIDocument;
+
+namespace mozilla {
+
+class TouchManager {
+public:
+ // Initialize and release static variables
+ static void InitializeStatics();
+ static void ReleaseStatics();
+
+ void Init(PresShell* aPresShell, nsIDocument* aDocument);
+ void Destroy();
+
+ bool PreHandleEvent(mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ bool& aTouchIsNew,
+ bool& aIsHandlingUserInput,
+ nsCOMPtr<nsIContent>& aCurrentEventContent);
+
+ static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget();
+ static bool HasCapturedTouch(int32_t aId);
+ static already_AddRefed<dom::Touch> GetCapturedTouch(int32_t aId);
+
+private:
+ void EvictTouches();
+ static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
+ nsIDocument* aLimitToDocument = nullptr);
+ static void AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList);
+
+ RefPtr<PresShell> mPresShell;
+ nsCOMPtr<nsIDocument> mDocument;
+
+ struct TouchInfo
+ {
+ RefPtr<mozilla::dom::Touch> mTouch;
+ nsCOMPtr<nsIContent> mNonAnonymousTarget;
+ };
+ static nsDataHashtable<nsUint32HashKey, TouchInfo>* sCaptureTouchList;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(TouchManager_h_) */
diff --git a/layout/base/UnitTransforms.h b/layout/base/UnitTransforms.h
new file mode 100644
index 000000000..e3a5af2d2
--- /dev/null
+++ b/layout/base/UnitTransforms.h
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZ_UNIT_TRANSFORMS_H_
+#define MOZ_UNIT_TRANSFORMS_H_
+
+#include "Units.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/Maybe.h"
+#include "nsRegion.h"
+
+namespace mozilla {
+
+// Convenience functions for converting an entity from one strongly-typed
+// coordinate system to another without changing the values it stores (this
+// can be thought of as a cast).
+// To use these functions, you must provide a justification for each use!
+// Feel free to add more justifications to PixelCastJustification, along with
+// a comment that explains under what circumstances it is appropriate to use.
+
+enum class PixelCastJustification : uint8_t {
+ // For the root layer, Screen Pixel = Parent Layer Pixel.
+ ScreenIsParentLayerForRoot,
+ // On the layout side, Screen Pixel = LayoutDevice at the outer-window level.
+ LayoutDeviceIsScreenForBounds,
+ // For the root layer, Render Target Pixel = Parent Layer Pixel.
+ RenderTargetIsParentLayerForRoot,
+ // For the root composition size we want to view it as layer pixels in any layer
+ ParentLayerToLayerForRootComposition,
+ // The Layer coordinate space for one layer is the ParentLayer coordinate
+ // space for its children
+ MovingDownToChildren,
+ // The transform that is usually used to convert between two coordinate
+ // systems is not available (for example, because the object that stores it
+ // is being destroyed), so fall back to the identity.
+ TransformNotAvailable,
+ // When an OS event is initially constructed, its reference point is
+ // technically in screen pixels, as it has not yet accounted for any
+ // asynchronous transforms. This justification is for viewing the initial
+ // reference point as a screen point. The reverse is useful when synthetically
+ // created WidgetEvents need to be converted back to InputData.
+ LayoutDeviceIsScreenForUntransformedEvent,
+ // Similar to LayoutDeviceIsScreenForUntransformedEvent, PBrowser handles
+ // some widget/tab dimension information as the OS does -- in screen units.
+ LayoutDeviceIsScreenForTabDims,
+ // A combination of LayoutDeviceIsScreenForBounds and
+ // ScreenIsParentLayerForRoot, which is how we're using it.
+ LayoutDeviceIsParentLayerForRCDRSF,
+ // Used to treat the product of AsyncTransformComponentMatrix objects
+ // as an AsyncTransformMatrix. See the definitions of these matrices in
+ // LayersTypes.h for details.
+ MultipleAsyncTransforms,
+ // We have reason to believe a layer doesn't have a local transform.
+ // Should only be used if we've already checked or asserted this.
+ NoTransformOnLayer
+};
+
+template <class TargetUnits, class SourceUnits>
+gfx::CoordTyped<TargetUnits> ViewAs(const gfx::CoordTyped<SourceUnits>& aCoord, PixelCastJustification) {
+ return gfx::CoordTyped<TargetUnits>(aCoord.value);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {
+ return gfx::SizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntSizeTyped<TargetUnits> ViewAs(const gfx::IntSizeTyped<SourceUnits>& aSize, PixelCastJustification) {
+ return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfx::PointTyped<SourceUnits>& aPoint, PixelCastJustification) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntPointTyped<TargetUnits> ViewAs(const gfx::IntPointTyped<SourceUnits>& aPoint, PixelCastJustification) {
+ return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::RectTyped<TargetUnits> ViewAs(const gfx::RectTyped<SourceUnits>& aRect, PixelCastJustification) {
+ return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntRectTyped<TargetUnits> ViewAs(const gfx::IntRectTyped<SourceUnits>& aRect, PixelCastJustification) {
+ return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::MarginTyped<TargetUnits> ViewAs(const gfx::MarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
+ return gfx::MarginTyped<TargetUnits>(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntMarginTyped<TargetUnits> ViewAs(const gfx::IntMarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
+ return gfx::IntMarginTyped<TargetUnits>(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntRegionTyped<TargetUnits> ViewAs(const gfx::IntRegionTyped<SourceUnits>& aRegion, PixelCastJustification) {
+ return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(aRegion.ToUnknownRegion());
+}
+template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
+gfx::ScaleFactor<SourceUnits, NewTargetUnits> ViewTargetAs(
+ const gfx::ScaleFactor<SourceUnits, OldTargetUnits>& aScaleFactor,
+ PixelCastJustification) {
+ return gfx::ScaleFactor<SourceUnits, NewTargetUnits>(aScaleFactor.scale);
+}
+// Unlike the other functions in this category, this function takes the
+// target matrix type, rather than its source and target unit types, as
+// the explicit template argument, so an example invocation is:
+// ViewAs<ScreenToLayerMatrix4x4>(otherTypedMatrix, justification)
+// The reason is that if it took the source and target unit types as two
+// template arguments, there may be some confusion as to which is the
+// source and which is the target.
+template <class TargetMatrix, class SourceMatrixSourceUnits, class SourceMatrixTargetUnits>
+TargetMatrix ViewAs(
+ const gfx::Matrix4x4Typed<SourceMatrixSourceUnits, SourceMatrixTargetUnits>& aMatrix,
+ PixelCastJustification) {
+ return TargetMatrix::FromUnknownMatrix(aMatrix.ToUnknownMatrix());
+}
+
+// Convenience functions for casting untyped entities to typed entities.
+// Using these functions does not require a justification, but once we convert
+// all code to use strongly typed units they should not be needed any longer.
+template <class TargetUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfxPoint& aPoint) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfx::Point& aPoint) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::RectTyped<TargetUnits> ViewAs(const gfx::Rect& aRect) {
+ return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+template <class TargetUnits>
+gfx::IntSizeTyped<TargetUnits> ViewAs(const nsIntSize& aSize) {
+ return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits>
+gfx::IntPointTyped<TargetUnits> ViewAs(const nsIntPoint& aPoint) {
+ return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::IntRectTyped<TargetUnits> ViewAs(const nsIntRect& aRect) {
+ return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+template <class TargetUnits>
+gfx::IntRegionTyped<TargetUnits> ViewAs(const nsIntRegion& aRegion) {
+ return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(aRegion);
+}
+// Unlike the other functions in this category, this function takes the
+// target matrix type, rather than its source and target unit types, as
+// the template argument, so an example invocation is:
+// ViewAs<ScreenToLayerMatrix4x4>(untypedMatrix)
+// The reason is that if it took the source and target unit types as two
+// template arguments, there may be some confusion as to which is the
+// source and which is the target.
+template <class TypedMatrix>
+TypedMatrix ViewAs(const gfx::Matrix4x4& aMatrix) {
+ return TypedMatrix::FromUnknownMatrix(aMatrix);
+}
+
+// Convenience functions for transforming an entity from one strongly-typed
+// coordinate system to another using the provided transformation matrix.
+template <typename TargetUnits, typename SourceUnits>
+static gfx::PointTyped<TargetUnits>
+TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aPoint)
+{
+ return aTransform.TransformPoint(aPoint);
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntPointTyped<TargetUnits>
+TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntPointTyped<SourceUnits>& aPoint)
+{
+ return RoundedToInt(TransformBy(aTransform, gfx::PointTyped<SourceUnits>(aPoint)));
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::RectTyped<TargetUnits>
+TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::RectTyped<SourceUnits>& aRect)
+{
+ return aTransform.TransformBounds(aRect);
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntRectTyped<TargetUnits>
+TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRectTyped<SourceUnits>& aRect)
+{
+ return RoundedToInt(TransformBy(aTransform, gfx::RectTyped<SourceUnits>(aRect)));
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntRegionTyped<TargetUnits>
+TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRegionTyped<SourceUnits>& aRegion)
+{
+ return ViewAs<TargetUnits>(aRegion.ToUnknownRegion().Transform(
+ aTransform.ToUnknownMatrix()));
+}
+
+// Transform |aVector|, which is anchored at |aAnchor|, by the given transform
+// matrix, yielding a point in |TargetUnits|.
+// The anchor is necessary because with 3D tranforms, the location of the
+// vector can affect the result of the transform.
+template <typename TargetUnits, typename SourceUnits>
+static gfx::PointTyped<TargetUnits>
+TransformVector(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aVector,
+ const gfx::PointTyped<SourceUnits>& aAnchor)
+{
+ gfx::PointTyped<TargetUnits> transformedStart = TransformBy(aTransform, aAnchor);
+ gfx::PointTyped<TargetUnits> transformedEnd = TransformBy(aTransform, aAnchor + aVector);
+ return transformedEnd - transformedStart;
+}
+
+// UntransformBy() and UntransformVector() are like TransformBy() and
+// TransformVector(), respectively, but are intended for cases where
+// the transformation matrix is the inverse of a 3D projection. When
+// using such transforms, the resulting Point4D is only meaningful
+// if it has a positive w-coordinate. To handle this, these functions
+// return a Maybe object which contains a value if and only if the
+// result is meaningful
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::PointTyped<TargetUnits>>
+UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aPoint)
+{
+ gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(aPoint);
+ if (!point.HasPositiveWCoord()) {
+ return Nothing();
+ }
+ return Some(point.As2DPoint());
+}
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::IntPointTyped<TargetUnits>>
+UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntPointTyped<SourceUnits>& aPoint)
+{
+ gfx::PointTyped<SourceUnits> p = aPoint;
+ gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(p);
+ if (!point.HasPositiveWCoord()) {
+ return Nothing();
+ }
+ return Some(RoundedToInt(point.As2DPoint()));
+}
+
+// The versions of UntransformBy() that take a rectangle also take a clip,
+// which represents the bounds within which the target must fall. The
+// result of the transform is intersected with this clip, and is considered
+// meaningful if the intersection is not empty.
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::RectTyped<TargetUnits>>
+UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::RectTyped<SourceUnits>& aRect,
+ const gfx::RectTyped<TargetUnits>& aClip)
+{
+ gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
+ if (rect.IsEmpty()) {
+ return Nothing();
+ }
+ return Some(rect);
+}
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::IntRectTyped<TargetUnits>>
+UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRectTyped<SourceUnits>& aRect,
+ const gfx::IntRectTyped<TargetUnits>& aClip)
+{
+ gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
+ if (rect.IsEmpty()) {
+ return Nothing();
+ }
+ return Some(RoundedToInt(rect));
+}
+
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::PointTyped<TargetUnits>>
+UntransformVector(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aVector,
+ const gfx::PointTyped<SourceUnits>& aAnchor)
+{
+ gfx::Point4DTyped<TargetUnits> projectedAnchor = aTransform.ProjectPoint(aAnchor);
+ gfx::Point4DTyped<TargetUnits> projectedTarget = aTransform.ProjectPoint(aAnchor + aVector);
+ if (!projectedAnchor.HasPositiveWCoord() || !projectedTarget.HasPositiveWCoord()){
+ return Nothing();
+ }
+ return Some(projectedTarget.As2DPoint() - projectedAnchor.As2DPoint());
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/Units.h b/layout/base/Units.h
new file mode 100644
index 000000000..199a91902
--- /dev/null
+++ b/layout/base/Units.h
@@ -0,0 +1,654 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZ_UNITS_H_
+#define MOZ_UNITS_H_
+
+#include "mozilla/gfx/Coord.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/ScaleFactor.h"
+#include "mozilla/gfx/ScaleFactors2D.h"
+#include "nsMargin.h"
+#include "nsRect.h"
+#include "nsRegion.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/TypeTraits.h"
+
+namespace mozilla {
+
+template <typename T>
+struct IsPixel : FalseType {};
+
+// See struct declaration for a description of each unit type.
+struct CSSPixel;
+struct LayoutDevicePixel;
+struct LayerPixel;
+struct CSSTransformedLayerPixel;
+struct RenderTargetPixel;
+struct ScreenPixel;
+struct ParentLayerPixel;
+struct DesktopPixel;
+
+template<> struct IsPixel<CSSPixel> : TrueType {};
+template<> struct IsPixel<LayoutDevicePixel> : TrueType {};
+template<> struct IsPixel<LayerPixel> : TrueType {};
+template<> struct IsPixel<CSSTransformedLayerPixel> : TrueType {};
+template<> struct IsPixel<RenderTargetPixel> : TrueType {};
+template<> struct IsPixel<ScreenPixel> : TrueType {};
+template<> struct IsPixel<ParentLayerPixel> : TrueType {};
+template<> struct IsPixel<DesktopPixel> : TrueType {};
+
+typedef gfx::CoordTyped<CSSPixel> CSSCoord;
+typedef gfx::IntCoordTyped<CSSPixel> CSSIntCoord;
+typedef gfx::PointTyped<CSSPixel> CSSPoint;
+typedef gfx::IntPointTyped<CSSPixel> CSSIntPoint;
+typedef gfx::SizeTyped<CSSPixel> CSSSize;
+typedef gfx::IntSizeTyped<CSSPixel> CSSIntSize;
+typedef gfx::RectTyped<CSSPixel> CSSRect;
+typedef gfx::IntRectTyped<CSSPixel> CSSIntRect;
+typedef gfx::MarginTyped<CSSPixel> CSSMargin;
+typedef gfx::IntMarginTyped<CSSPixel> CSSIntMargin;
+typedef gfx::IntRegionTyped<CSSPixel> CSSIntRegion;
+
+typedef gfx::CoordTyped<LayoutDevicePixel> LayoutDeviceCoord;
+typedef gfx::IntCoordTyped<LayoutDevicePixel> LayoutDeviceIntCoord;
+typedef gfx::PointTyped<LayoutDevicePixel> LayoutDevicePoint;
+typedef gfx::IntPointTyped<LayoutDevicePixel> LayoutDeviceIntPoint;
+typedef gfx::SizeTyped<LayoutDevicePixel> LayoutDeviceSize;
+typedef gfx::IntSizeTyped<LayoutDevicePixel> LayoutDeviceIntSize;
+typedef gfx::RectTyped<LayoutDevicePixel> LayoutDeviceRect;
+typedef gfx::IntRectTyped<LayoutDevicePixel> LayoutDeviceIntRect;
+typedef gfx::MarginTyped<LayoutDevicePixel> LayoutDeviceMargin;
+typedef gfx::IntMarginTyped<LayoutDevicePixel> LayoutDeviceIntMargin;
+typedef gfx::IntRegionTyped<LayoutDevicePixel> LayoutDeviceIntRegion;
+
+typedef gfx::CoordTyped<LayerPixel> LayerCoord;
+typedef gfx::IntCoordTyped<LayerPixel> LayerIntCoord;
+typedef gfx::PointTyped<LayerPixel> LayerPoint;
+typedef gfx::IntPointTyped<LayerPixel> LayerIntPoint;
+typedef gfx::SizeTyped<LayerPixel> LayerSize;
+typedef gfx::IntSizeTyped<LayerPixel> LayerIntSize;
+typedef gfx::RectTyped<LayerPixel> LayerRect;
+typedef gfx::IntRectTyped<LayerPixel> LayerIntRect;
+typedef gfx::MarginTyped<LayerPixel> LayerMargin;
+typedef gfx::IntMarginTyped<LayerPixel> LayerIntMargin;
+typedef gfx::IntRegionTyped<LayerPixel> LayerIntRegion;
+
+typedef gfx::CoordTyped<CSSTransformedLayerPixel> CSSTransformedLayerCoord;
+typedef gfx::IntCoordTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntCoord;
+typedef gfx::PointTyped<CSSTransformedLayerPixel> CSSTransformedLayerPoint;
+typedef gfx::IntPointTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntPoint;
+typedef gfx::SizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerSize;
+typedef gfx::IntSizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntSize;
+typedef gfx::RectTyped<CSSTransformedLayerPixel> CSSTransformedLayerRect;
+typedef gfx::IntRectTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntRect;
+typedef gfx::MarginTyped<CSSTransformedLayerPixel> CSSTransformedLayerMargin;
+typedef gfx::IntMarginTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntMargin;
+typedef gfx::IntRegionTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntRegion;
+
+typedef gfx::PointTyped<RenderTargetPixel> RenderTargetPoint;
+typedef gfx::IntPointTyped<RenderTargetPixel> RenderTargetIntPoint;
+typedef gfx::SizeTyped<RenderTargetPixel> RenderTargetSize;
+typedef gfx::IntSizeTyped<RenderTargetPixel> RenderTargetIntSize;
+typedef gfx::RectTyped<RenderTargetPixel> RenderTargetRect;
+typedef gfx::IntRectTyped<RenderTargetPixel> RenderTargetIntRect;
+typedef gfx::MarginTyped<RenderTargetPixel> RenderTargetMargin;
+typedef gfx::IntMarginTyped<RenderTargetPixel> RenderTargetIntMargin;
+typedef gfx::IntRegionTyped<RenderTargetPixel> RenderTargetIntRegion;
+
+typedef gfx::CoordTyped<ScreenPixel> ScreenCoord;
+typedef gfx::IntCoordTyped<ScreenPixel> ScreenIntCoord;
+typedef gfx::PointTyped<ScreenPixel> ScreenPoint;
+typedef gfx::IntPointTyped<ScreenPixel> ScreenIntPoint;
+typedef gfx::SizeTyped<ScreenPixel> ScreenSize;
+typedef gfx::IntSizeTyped<ScreenPixel> ScreenIntSize;
+typedef gfx::RectTyped<ScreenPixel> ScreenRect;
+typedef gfx::IntRectTyped<ScreenPixel> ScreenIntRect;
+typedef gfx::MarginTyped<ScreenPixel> ScreenMargin;
+typedef gfx::IntMarginTyped<ScreenPixel> ScreenIntMargin;
+typedef gfx::IntRegionTyped<ScreenPixel> ScreenIntRegion;
+
+typedef gfx::CoordTyped<ParentLayerPixel> ParentLayerCoord;
+typedef gfx::IntCoordTyped<ParentLayerPixel> ParentLayerIntCoord;
+typedef gfx::PointTyped<ParentLayerPixel> ParentLayerPoint;
+typedef gfx::IntPointTyped<ParentLayerPixel> ParentLayerIntPoint;
+typedef gfx::SizeTyped<ParentLayerPixel> ParentLayerSize;
+typedef gfx::IntSizeTyped<ParentLayerPixel> ParentLayerIntSize;
+typedef gfx::RectTyped<ParentLayerPixel> ParentLayerRect;
+typedef gfx::IntRectTyped<ParentLayerPixel> ParentLayerIntRect;
+typedef gfx::MarginTyped<ParentLayerPixel> ParentLayerMargin;
+typedef gfx::IntMarginTyped<ParentLayerPixel> ParentLayerIntMargin;
+typedef gfx::IntRegionTyped<ParentLayerPixel> ParentLayerIntRegion;
+
+typedef gfx::CoordTyped<DesktopPixel> DesktopCoord;
+typedef gfx::IntCoordTyped<DesktopPixel> DesktopIntCoord;
+typedef gfx::PointTyped<DesktopPixel> DesktopPoint;
+typedef gfx::IntPointTyped<DesktopPixel> DesktopIntPoint;
+typedef gfx::SizeTyped<DesktopPixel> DesktopSize;
+typedef gfx::IntSizeTyped<DesktopPixel> DesktopIntSize;
+typedef gfx::RectTyped<DesktopPixel> DesktopRect;
+typedef gfx::IntRectTyped<DesktopPixel> DesktopIntRect;
+
+typedef gfx::ScaleFactor<CSSPixel, LayoutDevicePixel> CSSToLayoutDeviceScale;
+typedef gfx::ScaleFactor<CSSPixel, LayerPixel> CSSToLayerScale;
+typedef gfx::ScaleFactor<CSSPixel, ScreenPixel> CSSToScreenScale;
+typedef gfx::ScaleFactor<CSSPixel, ParentLayerPixel> CSSToParentLayerScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, CSSPixel> LayoutDeviceToCSSScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, LayerPixel> LayoutDeviceToLayerScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, ScreenPixel> LayoutDeviceToScreenScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, ParentLayerPixel> LayoutDeviceToParentLayerScale;
+typedef gfx::ScaleFactor<LayerPixel, CSSPixel> LayerToCSSScale;
+typedef gfx::ScaleFactor<LayerPixel, LayoutDevicePixel> LayerToLayoutDeviceScale;
+typedef gfx::ScaleFactor<LayerPixel, RenderTargetPixel> LayerToRenderTargetScale;
+typedef gfx::ScaleFactor<LayerPixel, ScreenPixel> LayerToScreenScale;
+typedef gfx::ScaleFactor<LayerPixel, ParentLayerPixel> LayerToParentLayerScale;
+typedef gfx::ScaleFactor<RenderTargetPixel, ScreenPixel> RenderTargetToScreenScale;
+typedef gfx::ScaleFactor<ScreenPixel, CSSPixel> ScreenToCSSScale;
+typedef gfx::ScaleFactor<ScreenPixel, LayoutDevicePixel> ScreenToLayoutDeviceScale;
+typedef gfx::ScaleFactor<ScreenPixel, LayerPixel> ScreenToLayerScale;
+typedef gfx::ScaleFactor<ScreenPixel, ParentLayerPixel> ScreenToParentLayerScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, LayerPixel> ParentLayerToLayerScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, ScreenPixel> ParentLayerToScreenScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerScale;
+typedef gfx::ScaleFactor<DesktopPixel, LayoutDevicePixel> DesktopToLayoutDeviceScale;
+
+typedef gfx::ScaleFactors2D<CSSPixel, LayoutDevicePixel> CSSToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, LayerPixel> CSSToLayerScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, ScreenPixel> CSSToScreenScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, ParentLayerPixel> CSSToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, CSSPixel> LayoutDeviceToCSSScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, LayerPixel> LayoutDeviceToLayerScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, ScreenPixel> LayoutDeviceToScreenScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, ParentLayerPixel> LayoutDeviceToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, CSSPixel> LayerToCSSScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, LayoutDevicePixel> LayerToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, RenderTargetPixel> LayerToRenderTargetScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, ScreenPixel> LayerToScreenScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, ParentLayerPixel> LayerToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<RenderTargetPixel, ScreenPixel> RenderTargetToScreenScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, CSSPixel> ScreenToCSSScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, LayoutDevicePixel> ScreenToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, LayerPixel> ScreenToLayerScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, ParentLayerPixel> ScreenToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, LayerPixel> ParentLayerToLayerScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, ScreenPixel> ParentLayerToScreenScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerScale2D;
+
+typedef gfx::Matrix4x4Typed<LayoutDevicePixel, LayoutDevicePixel> LayoutDeviceToLayoutDeviceMatrix4x4;
+typedef gfx::Matrix4x4Typed<LayerPixel, ParentLayerPixel> LayerToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ScreenPixel, ScreenPixel> ScreenToScreenMatrix4x4;
+typedef gfx::Matrix4x4Typed<ScreenPixel, ParentLayerPixel> ScreenToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, LayerPixel> ParentLayerToLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, ScreenPixel> ParentLayerToScreenMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerMatrix4x4;
+
+/*
+ * The pixels that content authors use to specify sizes in.
+ */
+struct CSSPixel {
+
+ // Conversions from app units
+
+ static CSSPoint FromAppUnits(const nsPoint& aPoint) {
+ return CSSPoint(NSAppUnitsToFloatPixels(aPoint.x, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aPoint.y, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSSize FromAppUnits(const nsSize& aSize) {
+ return CSSSize(NSAppUnitsToFloatPixels(aSize.width, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aSize.height, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSRect FromAppUnits(const nsRect& aRect) {
+ return CSSRect(NSAppUnitsToFloatPixels(aRect.x, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aRect.y, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aRect.width, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aRect.height, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSMargin FromAppUnits(const nsMargin& aMargin) {
+ return CSSMargin(NSAppUnitsToFloatPixels(aMargin.top, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aMargin.right, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aMargin.bottom, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToFloatPixels(aMargin.left, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSIntPoint FromAppUnitsRounded(const nsPoint& aPoint) {
+ return CSSIntPoint(NSAppUnitsToIntPixels(aPoint.x, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToIntPixels(aPoint.y, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSIntSize FromAppUnitsRounded(const nsSize& aSize)
+ {
+ return CSSIntSize(NSAppUnitsToIntPixels(aSize.width, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToIntPixels(aSize.height, float(AppUnitsPerCSSPixel())));
+ }
+
+ static CSSIntRect FromAppUnitsRounded(const nsRect& aRect) {
+ return CSSIntRect(NSAppUnitsToIntPixels(aRect.x, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToIntPixels(aRect.y, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToIntPixels(aRect.width, float(AppUnitsPerCSSPixel())),
+ NSAppUnitsToIntPixels(aRect.height, float(AppUnitsPerCSSPixel())));
+ }
+
+ // Conversions to app units
+
+ static nsPoint ToAppUnits(const CSSPoint& aPoint) {
+ return nsPoint(NSToCoordRoundWithClamp(aPoint.x * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(aPoint.y * float(AppUnitsPerCSSPixel())));
+ }
+
+ static nsPoint ToAppUnits(const CSSIntPoint& aPoint) {
+ return nsPoint(NSToCoordRoundWithClamp(float(aPoint.x) * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(float(aPoint.y) * float(AppUnitsPerCSSPixel())));
+ }
+
+ static nsSize ToAppUnits(const CSSSize& aSize) {
+ return nsSize(NSToCoordRoundWithClamp(aSize.width * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(aSize.height * float(AppUnitsPerCSSPixel())));
+ }
+
+ static nsSize ToAppUnits(const CSSIntSize& aSize) {
+ return nsSize(NSToCoordRoundWithClamp(float(aSize.width) * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(float(aSize.height) * float(AppUnitsPerCSSPixel())));
+ }
+
+ static nsRect ToAppUnits(const CSSRect& aRect) {
+ return nsRect(NSToCoordRoundWithClamp(aRect.x * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(aRect.y * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(aRect.width * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(aRect.height * float(AppUnitsPerCSSPixel())));
+ }
+
+ static nsRect ToAppUnits(const CSSIntRect& aRect) {
+ return nsRect(NSToCoordRoundWithClamp(float(aRect.x) * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(float(aRect.y) * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(float(aRect.width) * float(AppUnitsPerCSSPixel())),
+ NSToCoordRoundWithClamp(float(aRect.height) * float(AppUnitsPerCSSPixel())));
+ }
+};
+
+/*
+ * The pixels that are referred to as "device pixels" in layout code. In
+ * general values measured in LayoutDevicePixels are obtained by dividing a
+ * value in app units by AppUnitsPerDevPixel(). Conversion between CSS pixels
+ * and LayoutDevicePixels is affected by:
+ * 1) the "full zoom" (see nsPresContext::SetFullZoom)
+ * 2) the "widget scale" (see nsIWidget::GetDefaultScale)
+ */
+struct LayoutDevicePixel {
+ static LayoutDeviceRect FromAppUnits(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceRect(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerDevPixel)),
+ NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerDevPixel)),
+ NSAppUnitsToFloatPixels(aRect.width, float(aAppUnitsPerDevPixel)),
+ NSAppUnitsToFloatPixels(aRect.height, float(aAppUnitsPerDevPixel)));
+ }
+
+ static LayoutDevicePoint FromAppUnits(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDevicePoint(NSAppUnitsToFloatPixels(aPoint.x, aAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aPoint.y, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceMargin FromAppUnits(const nsMargin& aMargin, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceMargin(NSAppUnitsToFloatPixels(aMargin.top, aAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aMargin.right, aAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aMargin.bottom, aAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aMargin.left, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntPoint FromAppUnitsRounded(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntPoint(NSAppUnitsToIntPixels(aPoint.x, aAppUnitsPerDevPixel),
+ NSAppUnitsToIntPixels(aPoint.y, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntPoint FromAppUnitsToNearest(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntPoint::FromUnknownPoint(aPoint.ToNearestPixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsToNearest(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect::FromUnknownRect(aRect.ToNearestPixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsToInside(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect::FromUnknownRect(aRect.ToInsidePixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntSize FromAppUnitsRounded(const nsSize& aSize, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntSize(
+ NSAppUnitsToIntPixels(aSize.width, aAppUnitsPerDevPixel),
+ NSAppUnitsToIntPixels(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static nsPoint ToAppUnits(const LayoutDeviceIntPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return nsPoint(aPoint.x * aAppUnitsPerDevPixel,
+ aPoint.y * aAppUnitsPerDevPixel);
+ }
+
+ static nsSize ToAppUnits(const LayoutDeviceIntSize& aSize, nscoord aAppUnitsPerDevPixel) {
+ return nsSize(aSize.width * aAppUnitsPerDevPixel,
+ aSize.height * aAppUnitsPerDevPixel);
+ }
+
+ static nsSize ToAppUnits(const LayoutDeviceSize& aSize, nscoord aAppUnitsPerDevPixel) {
+ return nsSize(NSFloatPixelsToAppUnits(aSize.width, aAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static nsRect ToAppUnits(const LayoutDeviceIntRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return nsRect(aRect.x * aAppUnitsPerDevPixel,
+ aRect.y * aAppUnitsPerDevPixel,
+ aRect.width * aAppUnitsPerDevPixel,
+ aRect.height * aAppUnitsPerDevPixel);
+ }
+
+ static nsRect ToAppUnits(const LayoutDeviceRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return nsRect(NSFloatPixelsToAppUnits(aRect.x, aAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(aRect.y, aAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(aRect.width, aAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(aRect.height, aAppUnitsPerDevPixel));
+ }
+};
+
+/*
+ * The pixels that layout rasterizes and delivers to the graphics code.
+ * These also are generally referred to as "device pixels" in layout code.
+ * Conversion between CSS pixels and LayerPixels is affected by:
+ * 1) the "display resolution" (see nsIPresShell::SetResolution)
+ * 2) the "full zoom" (see nsPresContext::SetFullZoom)
+ * 3) the "widget scale" (see nsIWidget::GetDefaultScale)
+ * 4) rasterizing at a different scale in the presence of some CSS transforms
+ */
+struct LayerPixel {
+};
+
+/*
+ * This is Layer coordinates with the Layer's CSS transform applied.
+ * It can be thought of as intermediate between LayerPixel and
+ * ParentLayerPixel, as further applying async transforms to this
+ * yields ParentLayerPixels.
+ */
+struct CSSTransformedLayerPixel {
+
+};
+
+/*
+ * Layers are always composited to a render target. This unit
+ * represents one pixel in the render target. Note that for the
+ * root render target RenderTargetPixel == ScreenPixel. Also
+ * any ContainerLayer providing an intermediate surface will
+ * have RenderTargetPixel == LayerPixel.
+ */
+struct RenderTargetPixel {
+};
+
+/*
+ * The pixels that are displayed on the screen.
+ * On non-OMTC platforms this should be equivalent to LayerPixel units.
+ * On OMTC platforms these may diverge from LayerPixel units temporarily,
+ * while an asynchronous zoom is happening, but should eventually converge
+ * back to LayerPixel units. Some variables (such as those representing
+ * chrome UI element sizes) that are not subject to content zoom should
+ * generally be represented in ScreenPixel units.
+ */
+struct ScreenPixel {
+};
+
+/* The layer coordinates of the parent frame.
+ * This can be arrived at in three ways:
+ * - Start with the CSS coordinates of the parent frame, multiply by the
+ * device scale and the cumulative resolution of the parent frame.
+ * - Start with the CSS coordinates of current frame, multiply by the device
+ * scale, the cumulative resolution of the current frame, and the scales
+ * from the CSS and async transforms of the current frame.
+ * - Start with global screen coordinates and unapply all CSS and async
+ * transforms from the root down to and including the parent.
+ * It's helpful to look at https://wiki.mozilla.org/Platform/GFX/APZ#Coordinate_systems
+ * to get a picture of how the various coordinate systems relate to each other.
+ */
+struct ParentLayerPixel {
+};
+
+/*
+ * Pixels in the coordinate space used by the host OS to manage windows on the
+ * desktop. What these mean varies between OSs:
+ * - by default (unless implemented differently in platform-specific widget
+ * code) they are the same as LayoutDevicePixels
+ * - on Mac OS X, they're "cocoa points", which correspond to device pixels
+ * on a non-Retina display, and to 2x device pixels on Retina
+ * - on Windows *without* per-monitor DPI support, they are Windows "logical
+ * pixels", i.e. device pixels scaled according to the Windows display DPI
+ * scaling factor (typically one of 1.25, 1.5, 1.75, 2.0...)
+ * - on Windows *with* per-monitor DPI support, they are physical device pixels
+ * on each screen; note that this means the scaling between CSS pixels and
+ * desktop pixels may vary across multiple displays.
+ */
+struct DesktopPixel {
+};
+
+// Operators to apply ScaleFactors directly to Coords, Points, Rects, Sizes and Margins
+
+template<class src, class dst>
+gfx::CoordTyped<dst> operator*(const gfx::CoordTyped<src>& aCoord, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::CoordTyped<dst>(aCoord.value * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::CoordTyped<dst> operator/(const gfx::CoordTyped<src>& aCoord, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::CoordTyped<dst>(aCoord.value / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator*(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::PointTyped<dst>(aPoint.x * aScale.scale,
+ aPoint.y * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator/(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::PointTyped<dst>(aPoint.x / aScale.scale,
+ aPoint.y / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator*(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::PointTyped<dst>(aPoint.x * aScale.xScale,
+ aPoint.y * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator/(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::PointTyped<dst>(aPoint.x / aScale.xScale,
+ aPoint.y / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator*(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::PointTyped<dst>(float(aPoint.x) * aScale.scale,
+ float(aPoint.y) * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator/(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::PointTyped<dst>(float(aPoint.x) / aScale.scale,
+ float(aPoint.y) / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator*(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::PointTyped<dst>(float(aPoint.x) * aScale.xScale,
+ float(aPoint.y) * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::PointTyped<dst> operator/(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::PointTyped<dst>(float(aPoint.x) / aScale.xScale,
+ float(aPoint.y) / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator*(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::RectTyped<dst>(aRect.x * aScale.scale,
+ aRect.y * aScale.scale,
+ aRect.width * aScale.scale,
+ aRect.height * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator/(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::RectTyped<dst>(aRect.x / aScale.scale,
+ aRect.y / aScale.scale,
+ aRect.width / aScale.scale,
+ aRect.height / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator*(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::RectTyped<dst>(aRect.x * aScale.xScale,
+ aRect.y * aScale.yScale,
+ aRect.width * aScale.xScale,
+ aRect.height * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator/(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::RectTyped<dst>(aRect.x / aScale.xScale,
+ aRect.y / aScale.yScale,
+ aRect.width / aScale.xScale,
+ aRect.height / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator*(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::RectTyped<dst>(float(aRect.x) * aScale.scale,
+ float(aRect.y) * aScale.scale,
+ float(aRect.width) * aScale.scale,
+ float(aRect.height) * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator/(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::RectTyped<dst>(float(aRect.x) / aScale.scale,
+ float(aRect.y) / aScale.scale,
+ float(aRect.width) / aScale.scale,
+ float(aRect.height) / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator*(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::RectTyped<dst>(float(aRect.x) * aScale.xScale,
+ float(aRect.y) * aScale.yScale,
+ float(aRect.width) * aScale.xScale,
+ float(aRect.height) * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::RectTyped<dst> operator/(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::RectTyped<dst>(float(aRect.x) / aScale.xScale,
+ float(aRect.y) / aScale.yScale,
+ float(aRect.width) / aScale.xScale,
+ float(aRect.height) / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator*(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::SizeTyped<dst>(aSize.width * aScale.scale,
+ aSize.height * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator/(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::SizeTyped<dst>(aSize.width / aScale.scale,
+ aSize.height / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator*(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::SizeTyped<dst>(aSize.width * aScale.xScale,
+ aSize.height * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator/(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::SizeTyped<dst>(aSize.width / aScale.xScale,
+ aSize.height / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator*(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::SizeTyped<dst>(float(aSize.width) * aScale.scale,
+ float(aSize.height) * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator/(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::SizeTyped<dst>(float(aSize.width) / aScale.scale,
+ float(aSize.height) / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator*(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::SizeTyped<dst>(float(aSize.width) * aScale.xScale,
+ float(aSize.height) * aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::SizeTyped<dst> operator/(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::SizeTyped<dst>(float(aSize.width) / aScale.xScale,
+ float(aSize.height) / aScale.yScale);
+}
+
+template<class src, class dst>
+gfx::MarginTyped<dst> operator*(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactor<src, dst>& aScale) {
+ return gfx::MarginTyped<dst>(aMargin.top * aScale.scale,
+ aMargin.right * aScale.scale,
+ aMargin.bottom * aScale.scale,
+ aMargin.left * aScale.scale);
+}
+
+template<class src, class dst>
+gfx::MarginTyped<dst> operator/(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactor<dst, src>& aScale) {
+ return gfx::MarginTyped<dst>(aMargin.top / aScale.scale,
+ aMargin.right / aScale.scale,
+ aMargin.bottom / aScale.scale,
+ aMargin.left / aScale.scale);
+}
+
+template<class src, class dst>
+gfx::MarginTyped<dst> operator*(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactors2D<src, dst>& aScale) {
+ return gfx::MarginTyped<dst>(aMargin.top * aScale.yScale,
+ aMargin.right * aScale.xScale,
+ aMargin.bottom * aScale.yScale,
+ aMargin.left * aScale.xScale);
+}
+
+template<class src, class dst>
+gfx::MarginTyped<dst> operator/(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactors2D<dst, src>& aScale) {
+ return gfx::MarginTyped<dst>(aMargin.top / aScale.yScale,
+ aMargin.right / aScale.xScale,
+ aMargin.bottom / aScale.yScale,
+ aMargin.left / aScale.xScale);
+}
+
+// Calculate the max or min or the ratios of the widths and heights of two
+// sizes, returning a scale factor in the correct units.
+
+template<class src, class dst>
+gfx::ScaleFactor<src, dst> MaxScaleRatio(const gfx::SizeTyped<dst>& aDestSize, const gfx::SizeTyped<src>& aSrcSize) {
+ return gfx::ScaleFactor<src, dst>(std::max(aDestSize.width / aSrcSize.width,
+ aDestSize.height / aSrcSize.height));
+}
+
+template<class src, class dst>
+gfx::ScaleFactor<src, dst> MinScaleRatio(const gfx::SizeTyped<dst>& aDestSize, const gfx::SizeTyped<src>& aSrcSize) {
+ return gfx::ScaleFactor<src, dst>(std::min(aDestSize.width / aSrcSize.width,
+ aDestSize.height / aSrcSize.height));
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/WordMovementType.h b/layout/base/WordMovementType.h
new file mode 100644
index 000000000..8d0c8e5d1
--- /dev/null
+++ b/layout/base/WordMovementType.h
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WordMovementType_h___
+#define WordMovementType_h___
+
+namespace mozilla {
+
+enum EWordMovementType { eStartWord, eEndWord, eDefaultBehavior };
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ZoomConstraintsClient.cpp b/layout/base/ZoomConstraintsClient.cpp
new file mode 100644
index 000000000..2391a116d
--- /dev/null
+++ b/layout/base/ZoomConstraintsClient.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ZoomConstraintsClient.h"
+
+#include <inttypes.h>
+#include "FrameMetrics.h"
+#include "gfxPrefs.h"
+#include "LayersLogging.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Event.h"
+#include "nsDocument.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPoint.h"
+#include "nsPresShell.h"
+#include "nsView.h"
+#include "nsViewportInfo.h"
+#include "Units.h"
+#include "UnitTransforms.h"
+
+#define ZCC_LOG(...)
+// #define ZCC_LOG(...) printf_stderr("ZCC: " __VA_ARGS__)
+
+NS_IMPL_ISUPPORTS(ZoomConstraintsClient, nsIDOMEventListener, nsIObserver)
+
+static const nsLiteralString DOM_META_ADDED = NS_LITERAL_STRING("DOMMetaAdded");
+static const nsLiteralString DOM_META_CHANGED = NS_LITERAL_STRING("DOMMetaChanged");
+static const nsLiteralString FULLSCREEN_CHANGED = NS_LITERAL_STRING("fullscreenchange");
+static const nsLiteralCString BEFORE_FIRST_PAINT = NS_LITERAL_CSTRING("before-first-paint");
+static const nsLiteralCString NS_PREF_CHANGED = NS_LITERAL_CSTRING("nsPref:changed");
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+ZoomConstraintsClient::ZoomConstraintsClient() :
+ mDocument(nullptr),
+ mPresShell(nullptr)
+{
+}
+
+ZoomConstraintsClient::~ZoomConstraintsClient()
+{
+}
+
+static nsIWidget*
+GetWidget(nsIPresShell* aShell)
+{
+ if (!aShell) {
+ return nullptr;
+ }
+ if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
+ return rootFrame->GetNearestWidget();
+#else
+ if (nsView* view = rootFrame->GetView()) {
+ return view->GetWidget();
+ }
+#endif
+ }
+ return nullptr;
+}
+
+void
+ZoomConstraintsClient::Destroy()
+{
+ if (!(mPresShell && mDocument)) {
+ return;
+ }
+
+ ZCC_LOG("Destroying %p\n", this);
+
+ if (mEventTarget) {
+ mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->RemoveSystemEventListener(FULLSCREEN_CHANGED, this, false);
+ mEventTarget = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
+ }
+
+ Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
+
+ if (mGuid) {
+ if (nsIWidget* widget = GetWidget(mPresShell)) {
+ ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64 " }\n",
+ this, mGuid->mPresShellId, mGuid->mScrollId);
+ widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, Nothing());
+ mGuid = Nothing();
+ }
+ }
+
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+void
+ZoomConstraintsClient::Init(nsIPresShell* aPresShell, nsIDocument* aDocument)
+{
+ if (!(aPresShell && aDocument)) {
+ return;
+ }
+
+ mPresShell = aPresShell;
+ mDocument = aDocument;
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ mEventTarget = window->GetParentTarget();
+ }
+ if (mEventTarget) {
+ mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->AddSystemEventListener(FULLSCREEN_CHANGED, this, false);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
+ }
+
+ Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
+}
+
+NS_IMETHODIMP
+ZoomConstraintsClient::HandleEvent(nsIDOMEvent* event)
+{
+ nsAutoString type;
+ event->GetType(type);
+
+ if (type.Equals(DOM_META_ADDED)) {
+ ZCC_LOG("Got a dom-meta-added event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (type.Equals(DOM_META_CHANGED)) {
+ ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (type.Equals(FULLSCREEN_CHANGED)) {
+ ZCC_LOG("Got a fullscreen-change event in %p\n", this);
+ RefreshZoomConstraints();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ZoomConstraintsClient::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (SameCOMIdentity(aSubject, mDocument) && BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
+ ZCC_LOG("Got a before-first-paint event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (NS_PREF_CHANGED.EqualsASCII(aTopic)) {
+ ZCC_LOG("Got a pref-change event in %p\n", this);
+ // We need to run this later because all the pref change listeners need
+ // to execute before we can be guaranteed that gfxPrefs::ForceUserScalable()
+ // returns the updated value.
+ NS_DispatchToMainThread(NewRunnableMethod(
+ this, &ZoomConstraintsClient::RefreshZoomConstraints));
+ }
+ return NS_OK;
+}
+
+void
+ZoomConstraintsClient::ScreenSizeChanged()
+{
+ ZCC_LOG("Got a screen-size change notification in %p\n", this);
+ RefreshZoomConstraints();
+}
+
+mozilla::layers::ZoomConstraints
+ComputeZoomConstraintsFromViewportInfo(const nsViewportInfo& aViewportInfo)
+{
+ mozilla::layers::ZoomConstraints constraints;
+ constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() && gfxPrefs::APZAllowZooming();
+ constraints.mAllowDoubleTapZoom = constraints.mAllowZoom;
+ if (constraints.mAllowZoom) {
+ constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale;
+ constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale;
+ } else {
+ constraints.mMinZoom.scale = aViewportInfo.GetDefaultZoom().scale;
+ constraints.mMaxZoom.scale = aViewportInfo.GetDefaultZoom().scale;
+ }
+ return constraints;
+}
+
+void
+ZoomConstraintsClient::RefreshZoomConstraints()
+{
+ nsIWidget* widget = GetWidget(mPresShell);
+ if (!widget) {
+ return;
+ }
+
+ uint32_t presShellId = 0;
+ FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
+ bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ mDocument->GetDocumentElement(),
+ &presShellId, &viewId);
+ if (!scrollIdentifiersValid) {
+ return;
+ }
+
+ LayoutDeviceIntSize screenSize;
+ if (!nsLayoutUtils::GetContentViewerSize(mPresShell->GetPresContext(), screenSize)) {
+ return;
+ }
+
+ nsViewportInfo viewportInfo = mDocument->GetViewportInfo(
+ ViewAs<ScreenPixel>(screenSize, PixelCastJustification::LayoutDeviceIsScreenForBounds));
+
+ mozilla::layers::ZoomConstraints zoomConstraints =
+ ComputeZoomConstraintsFromViewportInfo(viewportInfo);
+
+ if (mDocument->Fullscreen()) {
+ ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
+ zoomConstraints.mAllowZoom = false;
+ zoomConstraints.mAllowDoubleTapZoom = false;
+ }
+
+ if (zoomConstraints.mAllowDoubleTapZoom) {
+ // If the CSS viewport is narrower than the screen (i.e. width <= device-width)
+ // then we disable double-tap-to-zoom behaviour.
+ CSSToLayoutDeviceScale scale =
+ mPresShell->GetPresContext()->CSSToDevPixelScale();
+ if ((viewportInfo.GetSize() * scale).width <= screenSize.width) {
+ zoomConstraints.mAllowDoubleTapZoom = false;
+ }
+ }
+
+ // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
+ // the presShell must be the RCD-RSF (if it exists).
+ MOZ_ASSERT(mPresShell->GetPresContext()->IsRootContentDocument());
+ if (nsIScrollableFrame* rcdrsf = mPresShell->GetRootScrollFrameAsScrollable()) {
+ ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n", zoomConstraints.mAllowZoom);
+ rcdrsf->SetZoomableByAPZ(zoomConstraints.mAllowZoom);
+ }
+
+ ScrollableLayerGuid newGuid(0, presShellId, viewId);
+ if (mGuid && mGuid.value() != newGuid) {
+ ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64 " }\n",
+ this, mGuid->mPresShellId, mGuid->mScrollId);
+ // If the guid changes, send a message to clear the old one
+ widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, Nothing());
+ }
+ mGuid = Some(newGuid);
+ ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64 " }\n",
+ Stringify(zoomConstraints).c_str(), this, presShellId, viewId);
+ widget->UpdateZoomConstraints(presShellId, viewId, Some(zoomConstraints));
+}
diff --git a/layout/base/ZoomConstraintsClient.h b/layout/base/ZoomConstraintsClient.h
new file mode 100644
index 000000000..128ed73ea
--- /dev/null
+++ b/layout/base/ZoomConstraintsClient.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ZoomConstraintsClient_h_
+#define ZoomConstraintsClient_h_
+
+#include "FrameMetrics.h"
+#include "mozilla/Maybe.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+#include "nsWeakPtr.h"
+
+class nsIDOMEventTarget;
+class nsIDocument;
+class nsIPresShell;
+
+class ZoomConstraintsClient final : public nsIDOMEventListener,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIOBSERVER
+
+ ZoomConstraintsClient();
+
+private:
+ ~ZoomConstraintsClient();
+
+public:
+ void Init(nsIPresShell* aPresShell, nsIDocument *aDocument);
+ void Destroy();
+ void ScreenSizeChanged();
+
+private:
+ void RefreshZoomConstraints();
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
+ nsCOMPtr<nsIDOMEventTarget> mEventTarget;
+ mozilla::Maybe<mozilla::layers::ScrollableLayerGuid> mGuid;
+};
+
+#endif
+
diff --git a/layout/base/crashtests/1001237.html b/layout/base/crashtests/1001237.html
new file mode 100644
index 000000000..fa7d2f6a6
--- /dev/null
+++ b/layout/base/crashtests/1001237.html
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ <br id="x" style="transform-style: preserve-3d;">
+ <script>
+ document.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById("x").style.transform = "scale(2, 2)";
+ });
+ </script>
+ </body>
+</html>
diff --git a/layout/base/crashtests/1009036.html b/layout/base/crashtests/1009036.html
new file mode 100644
index 000000000..f9f22e3bc
--- /dev/null
+++ b/layout/base/crashtests/1009036.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ var div = document.getElementsByTagName("div")[0];
+ div.childNodes[1].convertPointFromNode({x:0, y:0}, div.childNodes[0]);
+}
+</script>
+</head>
+<body onload="boom();">
+<div><span>&#x05D0;C</span> </div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1043163-1.html b/layout/base/crashtests/1043163-1.html
new file mode 100644
index 000000000..d32b17a55
--- /dev/null
+++ b/layout/base/crashtests/1043163-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html style="mask: url(#none);"><canvas style="transform: scaleY(-118055395520340);"></canvas></html>
diff --git a/layout/base/crashtests/1061028.html b/layout/base/crashtests/1061028.html
new file mode 100644
index 000000000..98ea59f04
--- /dev/null
+++ b/layout/base/crashtests/1061028.html
@@ -0,0 +1,9 @@
+<style>td:first-letter {
+</style>
+><table border=0>
+ <td><table id=test1>><td id=test2>
+<script>
+setTimeout("tCFcrash()", 41);
+function tCFcrash() {
+test1.appendChild(test2);
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/1107508-1.html b/layout/base/crashtests/1107508-1.html
new file mode 100644
index 000000000..1ae6b1392
--- /dev/null
+++ b/layout/base/crashtests/1107508-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<svg id="s">
+ <style>
+ #b { display: none; }
+ rect { fill:orange; }
+ </style>
+ <rect width="10" height="10" fill="lime"/>
+</svg>
+<style>
+ #b { display: block; }
+ rect { fill:blue; }
+</style>
+<div id="b" style="border:2px solid black">
+ <svg>
+ <use xlink:href="#s"/>
+ </svg>
+</div>
diff --git a/layout/base/crashtests/1116104.html b/layout/base/crashtests/1116104.html
new file mode 100644
index 000000000..3f3f0169a
--- /dev/null
+++ b/layout/base/crashtests/1116104.html
@@ -0,0 +1,15 @@
+<html>
+
+<head>
+
+</head>
+
+<body>
+<style>colgroup::after { content:"after"; }</style>
+
+<table>
+<colgroup><col style="display: inline;">t</col></colgroup>
+</table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/1127198-1.html b/layout/base/crashtests/1127198-1.html
new file mode 100644
index 000000000..8f1524050
--- /dev/null
+++ b/layout/base/crashtests/1127198-1.html
@@ -0,0 +1,5 @@
+<style>.x { } .y { text-transform: uppercase; }</style><span id="I2">a<div id="I3">b</div></span><script>
+document.body.offsetTop;
+document.querySelector("span").className = "x";
+document.querySelector("div").className = "y";
+</script>
diff --git a/layout/base/crashtests/1140198.html b/layout/base/crashtests/1140198.html
new file mode 100644
index 000000000..2e3f075b4
--- /dev/null
+++ b/layout/base/crashtests/1140198.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.display = "contents";
+ document.designMode = 'on';
+ document.documentElement.insertAdjacentHTML("beforeEnd", "<span><optgroup>");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/1143535.html b/layout/base/crashtests/1143535.html
new file mode 100644
index 000000000..774984c71
--- /dev/null
+++ b/layout/base/crashtests/1143535.html
@@ -0,0 +1,6 @@
+<style>
+ body::before {
+ display: ruby;
+ content: " ";
+ }
+</style>
diff --git a/layout/base/crashtests/1156588.html b/layout/base/crashtests/1156588.html
new file mode 100644
index 000000000..ed0098f79
--- /dev/null
+++ b/layout/base/crashtests/1156588.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("layout.css.grid.enabled", true);
+-->
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("x").style.content = "'x'";
+ document.documentElement.offsetHeight;
+ document.getElementById("s").remove();
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="display: inline-grid; white-space: pre;"><div id="x"><span>
+<span>
+</span><span id="s"></span></span></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/1162813.xul b/layout/base/crashtests/1162813.xul
new file mode 100644
index 000000000..2ff652e79
--- /dev/null
+++ b/layout/base/crashtests/1162813.xul
@@ -0,0 +1,17 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("l").value="פיל\n";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(boom, 30)}, 0);
+
+</script>
+ <hbox dir="rtl">
+ <label id="l" />
+ </hbox>
+</window>
diff --git a/layout/base/crashtests/1163583.html b/layout/base/crashtests/1163583.html
new file mode 100644
index 000000000..d38e368d4
--- /dev/null
+++ b/layout/base/crashtests/1163583.html
@@ -0,0 +1,14 @@
+</body>
+<script type="text/javascript">
+function convertArrayToStrings(array){array.forEach(function(value,index){array[index]=String.fromCharCode(value);}); return array};
+var test0=document.body.appendChild(document.createElement("frame"))
+var test1=document.body.appendChild(document.createElement("figure"))
+var test2=document.body.appendChild(document.createElement("details"))
+var test4=document.body.appendChild(document.createElement("embed"))
+
+for(x=0;x<6;x++){
+test0.appendChild(document.createTextNode(convertArrayToStrings([38010,20080,40959,29079,56831,13899,8295]).join('')))
+test4.appendChild(test0.cloneNode(true));
+}
+
+</script>
diff --git a/layout/base/crashtests/118931-1.html b/layout/base/crashtests/118931-1.html
new file mode 100644
index 000000000..48a0bfa39
--- /dev/null
+++ b/layout/base/crashtests/118931-1.html
@@ -0,0 +1,7 @@
+<BODY>
+
+<DIV id=container style="POSITION: absolute;"></DIV>
+
+<SCRIPT language=Javascript>
+ document.getElementById('container').style.position='relative';
+</SCRIPT> \ No newline at end of file
diff --git a/layout/base/crashtests/121533-1.html b/layout/base/crashtests/121533-1.html
new file mode 100644
index 000000000..7cea9d659
--- /dev/null
+++ b/layout/base/crashtests/121533-1.html
@@ -0,0 +1,11 @@
+<html>
+<title>B#121533</title>
+<script>function writeSorry() {document.writeln("test");
+document.close();
+}
+</script>
+
+<frameset cols="120,*" onLoad="writeSorry()">
+<frame name="topslider" src="#"> <frame name="bottomslider" src="#">
+</frameset>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/123049-1.html b/layout/base/crashtests/123049-1.html
new file mode 100644
index 000000000..e4e51c58a
--- /dev/null
+++ b/layout/base/crashtests/123049-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style>
+#myStyle:-moz-display-comboboxcontrol-frame {
+ -moz-user-input: none !important;
+}
+#myStyle:-moz-dropdown-list {
+ -moz-user-input: none !important;
+}
+</style>
+</head>
+<body onload="getElementById('mySelect').setAttribute('id', 'myStyle');"><select id="mySelect"></select></body></html>
diff --git a/layout/base/crashtests/1234622-1.html b/layout/base/crashtests/1234622-1.html
new file mode 100644
index 000000000..c715bc18d
--- /dev/null
+++ b/layout/base/crashtests/1234622-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<script>
+
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ window.location = "data:text/html,2";
+ }, 0);
+}, false);
+
+window.addEventListener("pagehide", function() {
+ var x = document.createElement("object");
+ x.setAttribute("data", "data:text/plain,3");
+ document.documentElement.appendChild(x);
+}, false);
+
+</script>
diff --git a/layout/base/crashtests/1235467-1.html b/layout/base/crashtests/1235467-1.html
new file mode 100644
index 000000000..39a374b00
--- /dev/null
+++ b/layout/base/crashtests/1235467-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="transform: translateY(50%);">
+<div style="transform-style: preserve-3d; background-image: -moz-element(#a); position: sticky;" id="a">Q</div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/123946-1.html b/layout/base/crashtests/123946-1.html
new file mode 100644
index 000000000..0ed86427c
--- /dev/null
+++ b/layout/base/crashtests/123946-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>test</title>
+</head>
+
+<body>
+<div id="test" style="position: absolute;">test</div>
+<script type="application/x-javascript">document.getElementById("test").style.position = "fixed";</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/1261351-iframe.html b/layout/base/crashtests/1261351-iframe.html
new file mode 100644
index 000000000..82c1e25fa
--- /dev/null
+++ b/layout/base/crashtests/1261351-iframe.html
@@ -0,0 +1,26 @@
+<body>
+<script type="application/javascript">
+ 'use strict';
+ // -sp-context: content
+ (function () {
+ let proto = Object.create(HTMLDivElement.prototype);
+ proto.template = `<style></style>`;
+ proto.createdCallback = function() {
+ let shadow = this.createShadowRoot();
+ if (this.template) {
+ let te = document.createElement('template');
+ te.innerHTML = this.template;
+ shadow.appendChild(document.importNode(te.content, true));
+ }
+ };
+
+ let UiComponentTest = document.registerElement('ui-component-test', {
+ prototype: proto,
+ });
+
+ let uic = new UiComponentTest();
+ document.body.appendChild(uic);
+
+ })();
+</script>
+</body>
diff --git a/layout/base/crashtests/1261351.html b/layout/base/crashtests/1261351.html
new file mode 100644
index 000000000..70761652e
--- /dev/null
+++ b/layout/base/crashtests/1261351.html
@@ -0,0 +1,7 @@
+<iframe id="iframe" src="1261351-iframe.html"></iframe>
+<script type="application/javascript">
+ let iframe = document.getElementById("iframe");
+ iframe.addEventListener("load", function() {
+ document.getElementsByTagName("iframe")[0].marginWidth = "5";
+ });
+</script>
diff --git a/layout/base/crashtests/1270797-1.html b/layout/base/crashtests/1270797-1.html
new file mode 100644
index 000000000..8f9083c8c
--- /dev/null
+++ b/layout/base/crashtests/1270797-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="isolation:isolate; background-blend-mode:darken; background-image: url(green100x100.jpg)">
+ <div style="mix-blend-mode: multiply; width: 200px; height: 200px; background-color:red"></div>
+</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/1270797-1.jpg b/layout/base/crashtests/1270797-1.jpg
new file mode 100644
index 000000000..5b920f7c0
--- /dev/null
+++ b/layout/base/crashtests/1270797-1.jpg
Binary files differ
diff --git a/layout/base/crashtests/1278455-1.html b/layout/base/crashtests/1278455-1.html
new file mode 100644
index 000000000..470fea730
--- /dev/null
+++ b/layout/base/crashtests/1278455-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html style="transform: translateX(3px); display: grid;">
+<head>
+<!--
+user_pref("layout.event-regions.enabled", true);
+-->
+</head>
+<body>
+<div style="position: absolute;">Z</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1286889.html b/layout/base/crashtests/1286889.html
new file mode 100644
index 000000000..b39d009d4
--- /dev/null
+++ b/layout/base/crashtests/1286889.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<rt><span style="background:red;"><li></li>&#x200f;
diff --git a/layout/base/crashtests/128855-1.html b/layout/base/crashtests/128855-1.html
new file mode 100644
index 000000000..537fdf137
--- /dev/null
+++ b/layout/base/crashtests/128855-1.html
@@ -0,0 +1,8 @@
+<HTML><HEAD><TITLE>Testcase for bug 128855</TITLE></HEAD>
+<BODY>
+
+<P style="FONT-VARIANT: small-caps">2.3&nbsp;
+éÄÅÎÔÉÆÉËÁÔÏÒÙ.........................................................................................................................................................................
+</P>
+
+</BODY></HTML>
diff --git a/layout/base/crashtests/1288608.html b/layout/base/crashtests/1288608.html
new file mode 100644
index 000000000..52019a965
--- /dev/null
+++ b/layout/base/crashtests/1288608.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.body.style.overflow = "scroll";
+ c.style.visibility = "";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="c" style="position: relative; transition: 2s; display: table-cell; bottom: 0.1vw;"></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1297835.html b/layout/base/crashtests/1297835.html
new file mode 100644
index 000000000..47c9e3ea4
--- /dev/null
+++ b/layout/base/crashtests/1297835.html
@@ -0,0 +1,6 @@
+<body onload="document.documentElement.offsetWidth; document.querySelector('details').style.color = 'green'">
+ <details style="display: block; overflow: scroll;">
+ <summary>Some summary</summary>
+ The details
+ </details>
+</body>
diff --git a/layout/base/crashtests/1299736-1.html b/layout/base/crashtests/1299736-1.html
new file mode 100644
index 000000000..078a12037
--- /dev/null
+++ b/layout/base/crashtests/1299736-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Testcase, bug 1299736</title>
+
+<div id="A" style="transform: translateX(50px)">
+ <div id="B">
+ <div id="C" style="position: fixed">
+ </div>
+ </div>
+</div>
+
+<script>
+ document.getElementById("C").offsetLeft; // flush
+ document.getElementById("B").style.transform = "translateX(50px)";
+ document.getElementById("A").style.transform = "";
+</script>
diff --git a/layout/base/crashtests/1308793.svg b/layout/base/crashtests/1308793.svg
new file mode 100644
index 000000000..d2ba481cf
--- /dev/null
+++ b/layout/base/crashtests/1308793.svg
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<title>Crash test for bug 1308793</title>
+
+<style id="s" type="text/css">
+tspan { fill: green;}
+.flex { display:flex; }
+.grid { display:grid; }
+.col { columns: 3; }
+</style>
+
+<text class="flex">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+<text class="grid">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+<text class="col">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+</svg>
diff --git a/layout/base/crashtests/1308848-1.html b/layout/base/crashtests/1308848-1.html
new file mode 100644
index 000000000..894eb448a
--- /dev/null
+++ b/layout/base/crashtests/1308848-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<table><tbody></tbody><tfoot></tfoot></table>
+<script>
+ document.body.offsetTop;
+ let parent = document.querySelector("table");
+ let comment = document.createComment("hello");
+ let footer = document.querySelector("tfoot");
+ parent.insertBefore(comment, footer);
+</script>
diff --git a/layout/base/crashtests/1308848-2.html b/layout/base/crashtests/1308848-2.html
new file mode 100644
index 000000000..a83c395de
--- /dev/null
+++ b/layout/base/crashtests/1308848-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<table><tbody></tbody><tfoot></tfoot></table>
+<script>
+ document.body.offsetTop;
+ let parent = document.querySelector("table");
+ let pi = document.createProcessingInstruction('xml-stylesheet', 'href="test.css"');
+ let footer = document.querySelector("tfoot");
+ parent.insertBefore(pi, footer);
+</script>
diff --git a/layout/base/crashtests/133410-1.html b/layout/base/crashtests/133410-1.html
new file mode 100644
index 000000000..345efbd03
--- /dev/null
+++ b/layout/base/crashtests/133410-1.html
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <title>Bug 133410</title>
+ </head>
+
+ <body>
+
+ <table>
+ <tr>
+ <td>
+ <form>
+ <input type="text">
+ <input type="submit" value="Search">
+ <!-- note missing form close tag -->
+ </td>
+ </tr>
+ </table>
+
+ <table>
+ <span>
+ <!-- simple animated gif -->
+ <img src="../../../testing/crashtest/images/animfish.gif">
+ </span>
+ </table>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1343606.html b/layout/base/crashtests/1343606.html
new file mode 100644
index 000000000..ac1065600
--- /dev/null
+++ b/layout/base/crashtests/1343606.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+body {
+ columns: 5;
+ column-fill: auto;
+ height: 100px;
+}
+div {
+ display: grid;
+ grid-template-columns: 30px 30px 30px;
+ grid-auto-rows: 30px;
+ border:5px solid;
+}
+span {
+ border:1px solid black;
+}
+</style>
+<script>
+setTimeout(function(){ window.close(); },1000);
+window.onload = function(){
+ let a = document.getElementsByTagName("x")[0],
+ b = document.createTextNode("カ쾊紋鴺");
+ a.appendChild(b);
+ setTimeout(function(){
+ b.remove();
+ }, 0);
+};
+</script>
+</head>
+<body>
+<div>
+<span><x>æŸïžŸï¬¬í¤</x></span>
+The quick brown fox jumps over the lazy dog.
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-1a-inner.html b/layout/base/crashtests/143862-1a-inner.html
new file mode 100644
index 000000000..0313843b1
--- /dev/null
+++ b/layout/base/crashtests/143862-1a-inner.html
@@ -0,0 +1,19 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+window.addEventListener("load", o, false);
+function o()
+{
+ dump("143862-1-inner.html: B*\n");
+ document.documentElement.offsetHeight;
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1a.html b/layout/base/crashtests/143862-1a.html
new file mode 100644
index 000000000..099e1661c
--- /dev/null
+++ b/layout/base/crashtests/143862-1a.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1a-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-1b-inner.html b/layout/base/crashtests/143862-1b-inner.html
new file mode 100644
index 000000000..c81c02f41
--- /dev/null
+++ b/layout/base/crashtests/143862-1b-inner.html
@@ -0,0 +1,17 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+window.addEventListener("DOMContentLoaded", o, false);
+function o()
+{
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1b.html b/layout/base/crashtests/143862-1b.html
new file mode 100644
index 000000000..ec40fb0ad
--- /dev/null
+++ b/layout/base/crashtests/143862-1b.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1b-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-1c-inner.html b/layout/base/crashtests/143862-1c-inner.html
new file mode 100644
index 000000000..ed59d42e6
--- /dev/null
+++ b/layout/base/crashtests/143862-1c-inner.html
@@ -0,0 +1,17 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+o();
+function o()
+{
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1c.html b/layout/base/crashtests/143862-1c.html
new file mode 100644
index 000000000..8893c0c6d
--- /dev/null
+++ b/layout/base/crashtests/143862-1c.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1c-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-2.html b/layout/base/crashtests/143862-2.html
new file mode 100644
index 000000000..16c22b1df
--- /dev/null
+++ b/layout/base/crashtests/143862-2.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<title>Testcase, bug 143862</title>
+<style type="text/css" id="one"> html { overflow: hidden; } </style>
+<style type="text/css" id="two"></style>
+<script type="text/javascript">
+function remove(elt) { elt.parentNode.removeChild(elt); }
+function run() {
+ remove(document.getElementById("one"));
+ remove(document.getElementById("two"));
+
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(run, 100);
+</script>
+</html>
diff --git a/layout/base/crashtests/147320-1.html b/layout/base/crashtests/147320-1.html
new file mode 100644
index 000000000..f77d52383
--- /dev/null
+++ b/layout/base/crashtests/147320-1.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+ <fieldset style="position: absolute">
+ <legend>text</legend>
+ </fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/148245-1.html b/layout/base/crashtests/148245-1.html
new file mode 100644
index 000000000..749dc5db0
--- /dev/null
+++ b/layout/base/crashtests/148245-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+p:first-letter { float: left; }
+p:first-line { color: black; }
+</style>
+</head>
+<body>
+<p>Ly</p>
+</body>
+</html>
diff --git a/layout/base/crashtests/149014-1.html b/layout/base/crashtests/149014-1.html
new file mode 100644
index 000000000..e11f3b79f
--- /dev/null
+++ b/layout/base/crashtests/149014-1.html
@@ -0,0 +1,44 @@
+<html>
+<body>
+
+<center><h2><h2></center>
+<center><h2>1<h2></center>
+<center><h2>2<h2><center>
+<center><h2>3<ul><h2><center>
+<center><h2>4<h2><center>
+<center><h2>5<h2><center>
+<center><h2>6<h2><center>
+<center><h2>7<h2><center>
+<center><h2>8<h2><center>
+<center><h2>9<h2><center>
+<center><h2>10<h2><center>
+<center><h2>11<ul><h2><center>
+<center><h2>12<h2><center>
+<center><h2>13<h2><center>
+<center><h2>14<h2><center>
+<center><h2>15<h2><center>
+<center><h2>16<h2><center>
+<center><h2>17<h2><center>
+<center><h2>18<h2><center>
+<center><h2>19<h2><center>
+<center><h2>20<h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2><li>Test</li><h2><center>
+<center><h2><li>Test<font color=blue>( CD )</font></li><h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2>Test<center>
+<center><h2><h2><center>
+<center><h1></h1></center>
+<center><h3>.<h3><center>
+<center><h3><h3><center>
+<center><h2>Test<center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h1><center>
+
+<input type="text" name="maxbid" size="12" maxlength="12">
+</body>
+</html>
diff --git a/layout/base/crashtests/150431-1.html b/layout/base/crashtests/150431-1.html
new file mode 100644
index 000000000..9036cfe36
--- /dev/null
+++ b/layout/base/crashtests/150431-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<title>bug 150431</title>
+</head>
+<p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1>
+</body>
+</html>
diff --git a/layout/base/crashtests/176915-1.html b/layout/base/crashtests/176915-1.html
new file mode 100644
index 000000000..8b83a3e0e
--- /dev/null
+++ b/layout/base/crashtests/176915-1.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>bug 176915</title>
+ </head>
+ <body>
+ <div style='position:relative;display:inline'>
+ <object style='position:absolute;'></object>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/191272-1.html b/layout/base/crashtests/191272-1.html
new file mode 100644
index 000000000..6adac0789
--- /dev/null
+++ b/layout/base/crashtests/191272-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style>
+p:first-letter {
+ position: fixed;
+ left: 100px;
+ top: 100px;
+}
+</style>
+<body>
+<p>Blah blah blah
+</body>
+</html>
diff --git a/layout/base/crashtests/199696-1.html b/layout/base/crashtests/199696-1.html
new file mode 100644
index 000000000..f50fc487d
--- /dev/null
+++ b/layout/base/crashtests/199696-1.html
@@ -0,0 +1,33 @@
+<html>
+<head><title>bug 22037</title>
+
+ <!-- got the testcase from /mozilla/layout/html/tests/block/bugs/ -->
+
+</head>
+
+<body>
+
+
+<p><span><span><span>
+before before before before before before before before
+before before before before before before before before before before before
+before before before before before before before before before before before
+before before before before before before before before before before before before
+ <object src="foo">
+ left left left left left left left left left left left left left left
+ left left left left
+ <h2>
+ block block block block block block block block block block block block block
+ block block block block block block block
+ </h2>
+ right right right right right right right right right right right right right right right
+ right right right
+ </object>
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+</span></span></span></p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/217903-1.html b/layout/base/crashtests/217903-1.html
new file mode 100644
index 000000000..e6d308504
--- /dev/null
+++ b/layout/base/crashtests/217903-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<li>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/223064-1.html b/layout/base/crashtests/223064-1.html
new file mode 100644
index 000000000..e72ceda88
--- /dev/null
+++ b/layout/base/crashtests/223064-1.html
@@ -0,0 +1,11 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+<body>
+
+<script language="JavaScript" type="text/javascript">
+ document.writeln("<A><DIV STYLE=\"position:absolute;\">" + "</DIV></A>");
+</script>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/234851-1.html b/layout/base/crashtests/234851-1.html
new file mode 100644
index 000000000..56c3f3795
--- /dev/null
+++ b/layout/base/crashtests/234851-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<head>
+<title>Testcase</title>
+<style type="text/css">
+ html{
+ overflow:scroll;
+ }
+</style>
+</head>
+
+<body onload="var sheet = document.styleSheets[0]; sheet.disabled = true; sheet.disabled = false;">
+ Load this page to crash
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/234851-2.html b/layout/base/crashtests/234851-2.html
new file mode 100644
index 000000000..ee1790851
--- /dev/null
+++ b/layout/base/crashtests/234851-2.html
@@ -0,0 +1,35 @@
+<html style="overflow:scroll">
+<body>
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+</body>
+</html>
diff --git a/layout/base/crashtests/241300-1.html b/layout/base/crashtests/241300-1.html
new file mode 100644
index 000000000..5eb71ac9e
--- /dev/null
+++ b/layout/base/crashtests/241300-1.html
@@ -0,0 +1,5 @@
+<html><head></head>
+<body background="cid:00d201c264d0$feb75c80$0300a8c0@node3">
+</body>
+</html>
+
diff --git a/layout/base/crashtests/243159-1.html b/layout/base/crashtests/243159-1.html
new file mode 100644
index 000000000..94c2df5e9
--- /dev/null
+++ b/layout/base/crashtests/243159-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML PUBLIC "" []>
+<p style="display: table; white-space: nowrap; width: 400px; height: 100px">
+ <input type="text" style="display: table-cell;">
+ </p> \ No newline at end of file
diff --git a/layout/base/crashtests/243159-2.xhtml b/layout/base/crashtests/243159-2.xhtml
new file mode 100644
index 000000000..79d9bcd90
--- /dev/null
+++ b/layout/base/crashtests/243159-2.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+ <body onload="run()">
+ <mathml:math id="test" style="display: block">
+ </mathml:math>
+<script>
+ function run() {
+ var t1 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtable");
+ var t2 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtable");
+ var r1 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtr");
+ var r2 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtr");
+ var test =
+ document.getElementsByTagNameNS("http://www.w3.org/1998/Math/MathML", "math")[0];
+ t1.appendChild(r1);
+ test.appendChild(t1);
+ test.appendChild(t2);
+ t2.appendChild(r2);
+
+ }
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/243519-1.html b/layout/base/crashtests/243519-1.html
new file mode 100644
index 000000000..265241573
--- /dev/null
+++ b/layout/base/crashtests/243519-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <div style="position:absolute;">Hello</div>
+ <div style="position:fixed;">Kitty</div>
+ <script>
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+ document.body.offsetTop;
+
+ document.documentElement.style.position = "absolute";
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+ document.body.offsetTop;
+
+ document.documentElement.style.position = "fixed";
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+
+ document.documentElement.style.position = "";
+ document.body.offsetTop;
+ </script>
+</body>
+</html>
diff --git a/layout/base/crashtests/244490-1.html b/layout/base/crashtests/244490-1.html
new file mode 100644
index 000000000..366b03a1f
--- /dev/null
+++ b/layout/base/crashtests/244490-1.html
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xml:lang="de" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Crash Test</title>
+ <base href="D:\CSS Test Files\" />
+ <style type="text/css">
+ p { border: 1px red solid }
+ p:before { content: url("images/quote_end.png") }
+ </style>
+ </head>
+ <body>
+ <p>Did it crash?</p>
+ </body>
+</html>
diff --git a/layout/base/crashtests/254367-1.html b/layout/base/crashtests/254367-1.html
new file mode 100644
index 000000000..68b6acd43
--- /dev/null
+++ b/layout/base/crashtests/254367-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 254367</title>
+</head>
+<body>text<img> </body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/263359-1.html b/layout/base/crashtests/263359-1.html
new file mode 100644
index 000000000..cddd81b84
--- /dev/null
+++ b/layout/base/crashtests/263359-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <title>CSS Writing Modes Module Level 3</title>
+ <script type="text/javascript">
+function boom() {
+ document.getElementById("example").style.fontSize = "larger";
+}
+ </script>
+ </head>
+ <body onload=boom()>
+ <div id="example">
+ <p>×</p>
+ <pre><code>
+&lt;HEBREW&gt;
+ &lt;PAR&gt;HEBREW1 HEBREW2 english3 HEBREW4 HEBREW5&lt;/PAR&gt;
+ &lt;PAR&gt;HEBREW6 &lt;EMPH&gt;HEBREW7&lt;/EMPH&gt; HEBREW8&lt;/PAR&gt;
+&lt;/HEBREW&gt;
+&lt;ENGLISH&gt;
+ &lt;PAR&gt;english9 english10 english11 HEBREW12 HEBREW13&lt;/PAR&gt;
+ &lt;PAR&gt;english14 english15 english16&lt;/PAR&gt;
+ &lt;PAR&gt;english17 &lt;HE-QUO&gt;HEBREW18 english19 HEBREW20&lt;/HE-QUO&gt;&lt;/PAR&gt;
+&lt;/ENGLISH&gt;
+ </code></pre>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/265027-1.html b/layout/base/crashtests/265027-1.html
new file mode 100644
index 000000000..9b455da41
--- /dev/null
+++ b/layout/base/crashtests/265027-1.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+<MARQUEE>
+<TABLE>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<TBODY>
+Attack of the marquees!
+
+
diff --git a/layout/base/crashtests/265736-1.html b/layout/base/crashtests/265736-1.html
new file mode 100644
index 000000000..cecea66fd
--- /dev/null
+++ b/layout/base/crashtests/265736-1.html
@@ -0,0 +1,2 @@
+<HTML>
+<HR WIDTH=4444444 COLOR="#000000"> \ No newline at end of file
diff --git a/layout/base/crashtests/265736-2.html b/layout/base/crashtests/265736-2.html
new file mode 100644
index 000000000..2e5041b2d
--- /dev/null
+++ b/layout/base/crashtests/265736-2.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+
+<body>
+<iframe style="border-top-width: 31378748; border-bottom-right-radius: 23895784; ">
+</body>
+</html>
diff --git a/layout/base/crashtests/265899-1.html b/layout/base/crashtests/265899-1.html
new file mode 100644
index 000000000..e2fb197a1
--- /dev/null
+++ b/layout/base/crashtests/265899-1.html
@@ -0,0 +1,5 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY STYLE="float:right; HEIGHT:0pt; PADDING:99999999999px;"></BODY>
+</HTML>
diff --git a/layout/base/crashtests/265973-1.html b/layout/base/crashtests/265973-1.html
new file mode 100644
index 000000000..2ded7fb41
--- /dev/null
+++ b/layout/base/crashtests/265973-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<DIV STYLE="MARGIN:-99999999999px; PADDING:99999999999px; float:left; HEIGHT:0;"></DIV>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/265986-1.html b/layout/base/crashtests/265986-1.html
new file mode 100644
index 000000000..8d4ca290f
--- /dev/null
+++ b/layout/base/crashtests/265986-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<IFRAME STYLE="MARGIN:99999999999px; PADDING:-99999999999px;"></IFRAME>
+<APPLET STYLE="HEIGHT:9999999999pt; float:left; MARGIN:-99999999999px; border:99999999999px solid blue;"></APPLET>
+<MARQUEE STYLE=" WIDTH:9999999999px;">W</MARQUEE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/265999-1.html b/layout/base/crashtests/265999-1.html
new file mode 100644
index 000000000..7e6e3d416
--- /dev/null
+++ b/layout/base/crashtests/265999-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<MARQUEE STYLE="HEIGHT:9999999999px; float:right; border:99999999999px solid blue;"></MARQUEE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266222-1.html b/layout/base/crashtests/266222-1.html
new file mode 100644
index 000000000..0079a6b8a
--- /dev/null
+++ b/layout/base/crashtests/266222-1.html
@@ -0,0 +1,7 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<NOFRAMES STYLE="DISPLAY:BLOCK; float:left; overflow:inherit;"></NOFRAMES>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266360-1.html b/layout/base/crashtests/266360-1.html
new file mode 100644
index 000000000..30bdbb65b
--- /dev/null
+++ b/layout/base/crashtests/266360-1.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE=" border:10391122102cm solid #FFFFFF; float:right;">
+<SPAN STYLE=" border:inherit;"></SPAN>
+<H1 STYLE="float:right; HEIGHT:613927841cm; border:inherit;">Test</H1>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266445-1.html b/layout/base/crashtests/266445-1.html
new file mode 100644
index 000000000..1d79327d5
--- /dev/null
+++ b/layout/base/crashtests/266445-1.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE="overflow:hidden;">
+<HR STYLE="float:right; padding:71155995130em;">
+<OL STYLE="position:static;"><LI>Test</LI></OL>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266445-2.html b/layout/base/crashtests/266445-2.html
new file mode 100644
index 000000000..4de4e740b
--- /dev/null
+++ b/layout/base/crashtests/266445-2.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE="overflow:hidden;">
+<HR STYLE="float:right; height:2px; padding:71155995130em;">
+<OL STYLE="position:static;"><LI>Test</LI></OL>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/268157-1.html b/layout/base/crashtests/268157-1.html
new file mode 100644
index 000000000..5bdc494c6
--- /dev/null
+++ b/layout/base/crashtests/268157-1.html
@@ -0,0 +1,15 @@
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8
+<object>
+<div>
+</div>
+</object>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8
+
+<span>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+<object>
+<div>
+</div>
+</object>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+</span>
diff --git a/layout/base/crashtests/269566-1.html b/layout/base/crashtests/269566-1.html
new file mode 100644
index 000000000..35c63bcb1
--- /dev/null
+++ b/layout/base/crashtests/269566-1.html
@@ -0,0 +1,11 @@
+<html><head>
+<style>
+BODY { display:table; }
+</style>
+</head>
+<body>
+<div><iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%3C/body%3E%3C/html%3E"></iframe>
+</div>
+</body></html>
+
+
diff --git a/layout/base/crashtests/272647-1.html b/layout/base/crashtests/272647-1.html
new file mode 100644
index 000000000..f2fa5f2ea
--- /dev/null
+++ b/layout/base/crashtests/272647-1.html
@@ -0,0 +1,18 @@
+<html>
+ <header>
+ <title>Defects </title>
+ </header>
+<body>
+<center><table>
+<caption>
+</caption>
+
+<p>
+<caption>
+</tr></td>
+</center>
+<center><table>
+<td><tr>
+delete me and the problem goes away
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/275746-1.html b/layout/base/crashtests/275746-1.html
new file mode 100644
index 000000000..ea15adae1
--- /dev/null
+++ b/layout/base/crashtests/275746-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head><title>Testcase bug 275746 - Crash when clicking in drop down list, when changing from display:table-cell to display:inline</title>
+<style>
+span,select{display:table-cell;}
+</style>
+</head>
+<body onload="document.getElementById('x').style.display = 'inline'; document.documentElement.className = '';">
+<span>This is needed</span><select id='x'><option>option 1</option><option>option 2</option></select>
+</body></html>
diff --git a/layout/base/crashtests/276053-1.html b/layout/base/crashtests/276053-1.html
new file mode 100644
index 000000000..3155f0857
--- /dev/null
+++ b/layout/base/crashtests/276053-1.html
@@ -0,0 +1,21 @@
+<html><head><title>Testcase bug 276053 - Closeing a tab with http://linuxblog.sytes.net loaded in it causes Firefox to crash [@ nsView::GetDimensions]</title>
+<style>
+#serendipityRightSideBar {
+ display: block;
+}
+</style>
+</head>
+
+<body>
+<table><tbody><tr>
+<td>
+You should be able to see a green block at the right of this text<br>
+Closing this page, should not cause a crash.<br>
+
+<script>var x=document.body.offsetHeight;</script>
+</td>
+<td id="serendipityRightSideBar">
+ <iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%20style%3D%22background-color%3Agreen%22%3EYou%20should%20be%20able%20to%20see%20this%20text%3C/body%3E%3C/html%3E"></iframe>
+</td>
+</tr></tbody></table>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/280708-1.html b/layout/base/crashtests/280708-1.html
new file mode 100644
index 000000000..37ff83428
--- /dev/null
+++ b/layout/base/crashtests/280708-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head>
+<style>
+.rowg {display:table-row-group;}
+</style>
+</head><body onload="document.getElementById('x').className = 'rowg'; document.body.offsetWidth; document.getElementById('y').className = 'rowg'; document.body.offsetWidth; document.documentElement.className = '';">
+<table><tbody><tr>
+<td id="x"><input id="y"></td>
+</tr></tbody></table>
+</body></html>
diff --git a/layout/base/crashtests/280708-2.html b/layout/base/crashtests/280708-2.html
new file mode 100644
index 000000000..c5a94ab35
--- /dev/null
+++ b/layout/base/crashtests/280708-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head>
+<style>
+.rowg {display:table-row-group;}
+</style>
+</head><body onload="document.getElementById('x').className = 'rowg'; document.body.offsetWidth; document.getElementById('y').className = 'rowg'; document.body.offsetWidth; document.documentElement.className = '';">
+<table><tbody><tr>
+<td id="y"><input id="x"></td>
+</tr></tbody></table>
+</body></html>
diff --git a/layout/base/crashtests/281333-1.html b/layout/base/crashtests/281333-1.html
new file mode 100644
index 000000000..20d7ed9af
--- /dev/null
+++ b/layout/base/crashtests/281333-1.html
@@ -0,0 +1 @@
+<NOFRAMES STYLE="display:table-header-group; clear:inherit;"></NOFRAMES>
diff --git a/layout/base/crashtests/285212-1.html b/layout/base/crashtests/285212-1.html
new file mode 100644
index 000000000..3452839d9
--- /dev/null
+++ b/layout/base/crashtests/285212-1.html
@@ -0,0 +1,13 @@
+<BODY STYLE="margin:500px;">
+<DD>
+<OBJECT STYLE="width:500px;">
+<BODY>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+<UL>
+</UL>
+</BODY>
+</OBJECT>
+1
+</DD>
+</BODY>
diff --git a/layout/base/crashtests/286813-1.html b/layout/base/crashtests/286813-1.html
new file mode 100644
index 000000000..05010dc3f
--- /dev/null
+++ b/layout/base/crashtests/286813-1.html
@@ -0,0 +1,9 @@
+<HTML><HEAD><TITLE>286813</TITLE></HEAD><BODY>
+ <OBJECT>
+ <EMBED>12345678901234567890123456789123456789F<EMBED>
+ <OBJECT>
+ <IFRAME WIDTH="100"> frame </IFRAME>
+ </OBJECT>
+ </OBJECT>
+</BODY></HTML>
+ \ No newline at end of file
diff --git a/layout/base/crashtests/288790-1-inner.xhtml b/layout/base/crashtests/288790-1-inner.xhtml
new file mode 100644
index 000000000..15351dcff
--- /dev/null
+++ b/layout/base/crashtests/288790-1-inner.xhtml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<title>Testcase bug 288790 - Crash [@ GetNearestContainingBlock] with this xbl testcase</title>
+<head>
+<style>
+#z {position: relative;}
+#z span{position: absolute;}
+</style>
+
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="m"></binding>
+ <binding id="ma" extends="#m">
+ <content>
+ <html:div><children/></html:div>
+ </content>
+ </binding>
+</bindings>
+
+</head>
+<body>
+<div id="z"><span></span></div>
+
+
+<script>
+function doe(){
+document.getElementById('z').setAttribute('style','-moz-binding:url(#ma)');
+setTimeout(doe2,0);
+}
+
+function doe2(){
+document.getElementsByTagName('span')[0].setAttribute('style','-moz-binding:url(#m)');
+}
+</script>
+<button id="button" onclick="doe()">Click me</button><br/>
+Clicking on the above button two times, should not crash Mozilla.
+<script>
+function clickbutton()
+{
+ var ev = document.createEvent('MouseEvents');
+ ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ var button = document.getElementById('button');
+ button.dispatchEvent(ev);
+ button.dispatchEvent(ev);
+}
+clickbutton();
+</script>
+</body></html>
diff --git a/layout/base/crashtests/288790-1.html b/layout/base/crashtests/288790-1.html
new file mode 100644
index 000000000..de707506d
--- /dev/null
+++ b/layout/base/crashtests/288790-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="288790-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/306940-1.html b/layout/base/crashtests/306940-1.html
new file mode 100644
index 000000000..f6197c37c
--- /dev/null
+++ b/layout/base/crashtests/306940-1.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+
+<script>
+
+function init()
+{
+ var c1 = document.getElementById("c1");
+ var f1 = document.getElementById("f1");
+ var a1 = document.getElementById("a1");
+
+ function first()
+ {
+ f1.style.display = "-moz-popup";
+ c1.style.height = "2em";
+ window.status = "A";
+ }
+
+ function second()
+ {
+ c1.style.position = "absolute";
+ c1.style.overflow = "auto";
+ a1.style.position = "absolute";
+ window.status = "B";
+ }
+
+ first();
+ document.documentElement.offsetHeight;
+ second();
+}
+
+</script>
+</head>
+
+<body onload="init();">
+ <div id="c1">
+ <div id="f1">
+ <table>
+ <tr>
+ <td>
+
+ <span id="a1">Foo</span>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/310267-1.xml b/layout/base/crashtests/310267-1.xml
new file mode 100644
index 000000000..fff0a6555
--- /dev/null
+++ b/layout/base/crashtests/310267-1.xml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;" class="reftest-wait"><script><![CDATA[
+
+function init() {
+ var docElt = document.documentElement;
+ var firstText = docElt.childNodes[1];
+ var div = docElt.childNodes[2];
+ var bidiText = div.childNodes[0];
+
+ function first()
+ {
+ docElt.insertBefore(div, firstText);
+ docElt.insertBefore(bidiText, div);
+ }
+
+ function second()
+ {
+ docElt.insertBefore(div, firstText);
+ docElt.appendChild(bidiText);
+ document.documentElement.removeAttribute("class");
+ }
+
+ first();
+ setTimeout(second, 100);
+
+}
+
+window.addEventListener("load", init, false);
+
+]]></script>
+
+A<div>׳
+Z</div></html> \ No newline at end of file
diff --git a/layout/base/crashtests/310638-1.svg b/layout/base/crashtests/310638-1.svg
new file mode 100644
index 000000000..54d5182c8
--- /dev/null
+++ b/layout/base/crashtests/310638-1.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"><script><![CDATA[
+
+function init()
+{
+ var div2 = document.getElementById("div2");
+ var div1 = document.getElementById("div1");
+ var docElt = document.documentElement;
+ var titleText = document.createTextNode("foo baz");
+
+ docElt.appendChild(div2);
+ div2.appendChild(titleText);
+
+ function second ()
+ {
+ div2.appendChild(div1);
+ removeNode(titleText);
+ removeNode(div2);
+ document.documentElement.removeAttribute("class");
+ }
+
+ setTimeout(second, 30);
+}
+
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+
+setTimeout(init, 30);
+
+
+]]></script>
+
+<div xmlns='http://www.w3.org/1999/xhtml' id="div1">
+
+<div id="div2">bar</div>
+</div>
+
+</svg>
diff --git a/layout/base/crashtests/310638-2.html b/layout/base/crashtests/310638-2.html
new file mode 100644
index 000000000..34bfc4968
--- /dev/null
+++ b/layout/base/crashtests/310638-2.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+
+</HEAD>
+<BODY onload="document.getElementById('s').removeAttribute('style');">
+<span>
+ <span style="display: block;" id="s">This should not crash Mozilla</span>
+</span>
+<div style=" position: absolute;">
+ <span style="position: relative;">
+ <span style="white-space:pre;">
+ <span style="position: absolute;">
+ <span style="float: right;"></span>
+ </span>
+ </span>
+ </span>
+</div>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/311661-1.xul b/layout/base/crashtests/311661-1.xul
new file mode 100644
index 000000000..6b49c690a
--- /dev/null
+++ b/layout/base/crashtests/311661-1.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Testcase bug 311661 - Evil xul testcase, using display:table-row causes crash [@ nsTableRowGroupFrame::GetFirstRow]">
+<html:script><![CDATA[
+function doe(i) {
+document.documentElement.getElementsByTagName('*')[i].style.display='table-row';
+document.documentElement.getElementsByTagName('*')[i+1].style.display='table-row';
+i+=1;
+setTimeout(doe2,20,i);
+}
+function doe2(i){
+document.documentElement.getElementsByTagName('*')[i-1].style.display='';
+if (i>1)i=1;
+setTimeout(doe,20,i);
+}
+]]></html:script>
+<button id="button" onclick="doe(1)" label="Mozilla should not crash, when clicking this button"/>
+<script/>
+<html:script>
+function clickbutton()
+{
+ var ev = document.createEvent('MouseEvents');
+ ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ var button = document.getElementById('button');
+ button.dispatchEvent(ev);
+
+ setTimeout(function() { document.documentElement.className = "" }, 500);
+}
+window.addEventListener("load", clickbutton, false);
+</html:script>
+
+</window>
diff --git a/layout/base/crashtests/311661-2.xul b/layout/base/crashtests/311661-2.xul
new file mode 100644
index 000000000..4ed2c8f2c
--- /dev/null
+++ b/layout/base/crashtests/311661-2.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Testcase bug 311661 - Evil xul testcase, using display:table-row causes crash [@ nsTableRowGroupFrame::GetFirstRow]">
+<html:script><![CDATA[
+function doe() {
+document.documentElement.getElementsByTagName('*')[1].style.display='table-row';
+setTimeout(doe2,20);
+}
+function doe2(){
+document.documentElement.getElementsByTagName('*')[1].style.display='';
+setTimeout(doe,20);
+}
+]]></html:script>
+<button id="button" onclick="doe()" label="Mozilla should not crash, when clicking this button"/>
+<div style="display:table-row"/>
+<html:script>
+function clickbutton()
+{
+ var ev = document.createEvent('MouseEvents');
+ ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ var button = document.getElementById('button');
+ button.dispatchEvent(ev);
+
+ setTimeout(function() { document.documentElement.className = "" }, 500);
+}
+window.addEventListener("load", clickbutton, false);
+</html:script>
+
+</window>
diff --git a/layout/base/crashtests/313086-1.xml b/layout/base/crashtests/313086-1.xml
new file mode 100644
index 000000000..5ebcf45ed
--- /dev/null
+++ b/layout/base/crashtests/313086-1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "xhtml1-strict.dtd">
+<html xmlns='http://www.w3.org/1999/xhtml' id="root" class="reftest-wait">
+
+<div id="D1"><div id="D2"/></div>
+
+<script>
+<![CDATA[
+
+function gE(id) { return document.getElementById(id); }
+
+function init()
+{
+ gE("root").style.display = "table";
+
+ gE("D1").style.position = "absolute";
+
+ setTimeout(function() {gE("D2").style.position = "fixed";}, 100);
+ setTimeout(function() {gE("D1").style.overflow = "hidden";}, 200);
+ setTimeout(function() {gE("root").style.width = "200%"; document.documentElement.removeAttribute("class"); }, 300);
+}
+
+window.addEventListener("load", init, false);
+
+]]>
+</script>
+
+</html>
diff --git a/layout/base/crashtests/317285-1.html b/layout/base/crashtests/317285-1.html
new file mode 100644
index 000000000..bcd84fe06
--- /dev/null
+++ b/layout/base/crashtests/317285-1.html
@@ -0,0 +1 @@
+<HEAD><BGSOUND STYLE="Ã" STYLE="Ó" LOOP="top" LOOP=í-> LOOP= LOOP=%n%n%n%n%n%n LOOP="E" onLoad="-m" SRCˆ%n%n%n%n%n%n STYLE=÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷ onLoad=# SRC=# SRC="-." STYLE=RRRRRRRR><IMG START=&; START="left"><BGSOUND STYLE=-í SRC="‡‡‡" STYLE="&"-$";" LOOP=(( SRC="javascript:"_self""-Ç LOOP=# STYLE=úú LOOP="8888" LOOP="-1""\\\\" SRC="ýýýýýý" SRC="-" SRC="w" LOOP="-¡" LOOP=öööööööö LOOP=çç STYLE=-Æ STYLE=""ææææ"è" STYLEl> \ No newline at end of file
diff --git a/layout/base/crashtests/317934-1-inner.html b/layout/base/crashtests/317934-1-inner.html
new file mode 100644
index 000000000..9c14d030d
--- /dev/null
+++ b/layout/base/crashtests/317934-1-inner.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<script>
+function clickit()
+{
+document.getElementById('button').click();
+}
+window.addEventListener('load', clickit, false);
+</script>
+</head>
+<body>
+<div style="width:400px;">
+<q style="position:relative;"><q style="position:relative;">
+Some random text, some random text, some random text
+<span style="position: relative;">
+Some random text, some random text, some random text
+</span>
+</q></q>
+</div>
+<script>
+function doe(){
+var q1=document.getElementsByTagName('q')[0];
+var q2=document.getElementsByTagName('q')[1];
+q1.style.position='static';
+q2.style.position='static';
+}
+//setTimeout(doe,200);
+</script>
+<button id="button" onclick="doe()">Clicking this button should not crash Mozilla</button>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/317934-1.html b/layout/base/crashtests/317934-1.html
new file mode 100644
index 000000000..ee77106c5
--- /dev/null
+++ b/layout/base/crashtests/317934-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="317934-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/320459-1.html b/layout/base/crashtests/320459-1.html
new file mode 100644
index 000000000..2448fa585
--- /dev/null
+++ b/layout/base/crashtests/320459-1.html
@@ -0,0 +1,7 @@
+ <legend>
+ <kbd>
+ <object>
+ <h4>
+ </object>
+ </kbd>
+
diff --git a/layout/base/crashtests/321058-1.xul b/layout/base/crashtests/321058-1.xul
new file mode 100644
index 000000000..1df88d19a
--- /dev/null
+++ b/layout/base/crashtests/321058-1.xul
@@ -0,0 +1,4 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <popupgroup>
+ </popupgroup>
+</window>
diff --git a/layout/base/crashtests/321058-2.xul b/layout/base/crashtests/321058-2.xul
new file mode 100644
index 000000000..a3bbb4110
--- /dev/null
+++ b/layout/base/crashtests/321058-2.xul
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30);">
+
+<script><![CDATA[
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var popupgroup = document.createElementNS(XUL_NS, 'popupgroup');
+ document.documentElement.appendChild(popupgroup);
+ document.documentElement.removeChild(popupgroup);
+
+ var tooltip = document.createElementNS(XUL_NS, 'tooltip');
+ document.documentElement.appendChild(tooltip);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]></script>
+
+</window>
diff --git a/layout/base/crashtests/321077-1.xul b/layout/base/crashtests/321077-1.xul
new file mode 100644
index 000000000..3cd650eac
--- /dev/null
+++ b/layout/base/crashtests/321077-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tree>
+ <treechildren/>
+ </tree>
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/321077-2.xul b/layout/base/crashtests/321077-2.xul
new file mode 100644
index 000000000..1e257ef87
--- /dev/null
+++ b/layout/base/crashtests/321077-2.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 0);">
+
+<script type="text/javascript">
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ // Fire off an image load, then leave while the image load is pending.
+
+ document.getElementById("image").src = "data:text/html,foo";
+ location = "data:text/html,elsewhere";
+}
+
+</script>
+
+<tree><treechildren/></tree><image id="image"/>
+
+</window>
diff --git a/layout/base/crashtests/322436-1.html b/layout/base/crashtests/322436-1.html
new file mode 100644
index 000000000..907ddddc1
--- /dev/null
+++ b/layout/base/crashtests/322436-1.html
@@ -0,0 +1,31 @@
+<html class="reftest-wait">
+
+<head>
+
+
+
+<script>
+
+function foo()
+{
+ setTimeout(bar, 30);
+}
+
+function bar()
+{
+ document.getElementById("TT").style.position = "absolute";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="foo();">
+
+
+<div id="TT"><div style="position: fixed;"><div style="display: -moz-box;"><div style="float: left;"></div></div></div></div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/325967-1.html b/layout/base/crashtests/325967-1.html
new file mode 100644
index 000000000..37d0ece80
--- /dev/null
+++ b/layout/base/crashtests/325967-1.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+
+<script>
+
+function init()
+{
+ var ww = document.getElementById("ww");
+ var inp = document.getElementById("inp");
+
+ document.addEventListener("DOMNodeInserted", u, false);
+
+ document.body.appendChild(ww);
+
+ function u()
+ {
+ document.removeEventListener("DOMNodeInserted", u, false);
+ ww.removeChild(inp);
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+</script>
+
+</head>
+
+<body onload="init()"><div id="ww"><input type="text" value="inputtext" id="inp">moretext</div></body>
+
+</html>
diff --git a/layout/base/crashtests/325984-1.xhtml b/layout/base/crashtests/325984-1.xhtml
new file mode 100644
index 000000000..eee6acff9
--- /dev/null
+++ b/layout/base/crashtests/325984-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body><table><col onload="3"/>foo</table></body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/325984-2.html b/layout/base/crashtests/325984-2.html
new file mode 100644
index 000000000..b17ef4197
--- /dev/null
+++ b/layout/base/crashtests/325984-2.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>colgroup pseudos</title>
+ <style>
+ div.table {background-color:red; color:yellow; display:table}
+ div.col {background-color:green; width:400px; display:table-column}
+
+ </style>
+ </head>
+ <body>
+ <div class="table">
+ <div class="col" ></div> anonymous content
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-cell">anonymous cell</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-row">anonymous row</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-row-group">anonymous rowgroup</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table">anonymous table</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-caption">anonymous caption</div>
+ </div>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/328944-1.xul b/layout/base/crashtests/328944-1.xul
new file mode 100644
index 000000000..5a5a2d4f5
--- /dev/null
+++ b/layout/base/crashtests/328944-1.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function gE(i) { return document.getElementById(i); }
+
+function init()
+{
+ gE("button").insertBefore(gE("popup"), gE("hbox4"));
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<menupopup id="popup"/>
+
+<button id="button"><hbox/><hbox/><hbox/><hbox id="hbox4"/></button>
+
+</window>
diff --git a/layout/base/crashtests/329900-1.html b/layout/base/crashtests/329900-1.html
new file mode 100644
index 000000000..54d702149
--- /dev/null
+++ b/layout/base/crashtests/329900-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Testcase bug 329900 - Crash with evil testcase, using table-column-group, table-column, table-cell</title>
+</head>
+<body>
+Mozilla should not crash with this page
+<div style="display: table-cell;">
+ <span style="display: table-cell;"></span>
+ <span style="display: table-column;"></span>
+ <span style="display: table-column-group;"></span>
+ <span style="display: table-cell;"></span>
+ <table></table>
+</div>
+
+</body></html>
diff --git a/layout/base/crashtests/330015-1.html b/layout/base/crashtests/330015-1.html
new file mode 100644
index 000000000..84e66edc5
--- /dev/null
+++ b/layout/base/crashtests/330015-1.html
@@ -0,0 +1,14 @@
+<html><head style="display: table-row;">
+<title>Testcase bug 330015 - Crash with display: table-column-group, table-row, table-column, etc</title>
+<link style="display: table-row;">
+<link style="display: block;">
+<link style="display: table-column;">
+<link style="display: table-column-group;">
+</head>
+<body>
+Mozilla should not crash on this page.
+<script>
+document.getElementsByTagName('head')[0].style.display = '';
+document.getElementsByTagName('link')[1].style.display = '';
+</script>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/331204-1.html b/layout/base/crashtests/331204-1.html
new file mode 100644
index 000000000..e210cb56a
--- /dev/null
+++ b/layout/base/crashtests/331204-1.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+
+<style>#stack{ display: -moz-stack; }</style>
+
+<span id="stack"><select></select><select>
+
+</select></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/331679-1.xhtml b/layout/base/crashtests/331679-1.xhtml
new file mode 100644
index 000000000..298949197
--- /dev/null
+++ b/layout/base/crashtests/331679-1.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Bug 331679 testcase</title>
+
+
+<style id="style">
+.cat::-moz-table-row-group { overflow: scroll; }
+.toad { position: absolute; }
+</style>
+
+<script>
+
+function init()
+{
+ document.getElementById("style").textContent += "table::-moz-table-row { opacity: 0.2; }";
+ document.getElementById("row").setAttribute("class", "toad");
+ document.getElementById("table").setAttribute("class", "cat");
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<table id="table">
+ <tr id="row">
+ <td>Cell</td>
+ </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/331679-2.xml b/layout/base/crashtests/331679-2.xml
new file mode 100644
index 000000000..7f4e8184a
--- /dev/null
+++ b/layout/base/crashtests/331679-2.xml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>pseudo double SetInitialChildlist</title>
+ <style>
+ .cat::-moz-table-row-group { overflow: scroll;}
+ tr { position: absolute;}
+ </style>
+ </head>
+
+ <body>
+
+ <table class="cat">
+ <tr>
+ <td>Cell</td>
+ </tr>
+ </table>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/331679-3.xml b/layout/base/crashtests/331679-3.xml
new file mode 100644
index 000000000..df73640be
--- /dev/null
+++ b/layout/base/crashtests/331679-3.xml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>pseudo double SetInitialChildlist</title>
+ <style>
+ .cat::-moz-table-row-group { overflow: scroll;}
+ tr { position: absolute;}
+ </style>
+ </head>
+
+ <body>
+
+ <div class="cat" style="display:table">
+ <div style="display:block">
+ <div style="display:table-cell">Cell</div>
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/331883-1-inner.html b/layout/base/crashtests/331883-1-inner.html
new file mode 100644
index 000000000..2189dff03
--- /dev/null
+++ b/layout/base/crashtests/331883-1-inner.html
@@ -0,0 +1,30 @@
+<html>
+
+<head style="display: none">
+
+<style id="style">
+.lizard:first-line { }
+</style>
+
+<script>
+
+function init()
+{
+ document.getElementById("style").textContent += "* { position: relative; }";
+ document.getElementById("comment10div").setAttribute("class", "lizard");
+ document.getElementById("style").textContent += "*::-moz-line-frame { position: absolute; }";
+ setTimeout(function() { location.reload(); }, 200);
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<div id="comment10div">XXXXXXXXXXXXXXXXXXXXXXXX <span>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ JJJJJJJJJJJJJJJ PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP</span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/331883-1.html b/layout/base/crashtests/331883-1.html
new file mode 100644
index 000000000..b0c2339dd
--- /dev/null
+++ b/layout/base/crashtests/331883-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<head>
+<script>
+var numLoads = 0;
+function loaded()
+{
+ numLoads++;
+ if (numLoads == 5) {
+ document.documentElement.className = "";
+ }
+}
+</script>
+<body>
+<iframe onload="loaded()" src="331883-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/335140-1.html b/layout/base/crashtests/335140-1.html
new file mode 100644
index 000000000..9ed0b8bd4
--- /dev/null
+++ b/layout/base/crashtests/335140-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+
+<body>
+
+<span style="position: relative;">
+ <br> <span style="position: absolute;">Login</span>
+</span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/336291-1.html b/layout/base/crashtests/336291-1.html
new file mode 100644
index 000000000..cbcb6c0c9
--- /dev/null
+++ b/layout/base/crashtests/336291-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script>
+function z()
+{
+ document.getElementById("x").style.display = "table";
+ document.body.style.display = "table-row";
+}
+</script>
+</head>
+
+<body onload="z()">
+
+<p style="display: table-row"></p>
+
+<p id="x"></p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/336999-1.xul b/layout/base/crashtests/336999-1.xul
new file mode 100644
index 000000000..981d54026
--- /dev/null
+++ b/layout/base/crashtests/336999-1.xul
@@ -0,0 +1,26 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("xxx").style.position = "fixed";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(boom, 30)}, 0);
+
+</script>
+
+
+ <hbox id="xxx" style="position: absolute;">
+ <label value="X" />
+ <menulist>
+ <menupopup>
+ <menuitem label="Y" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+
+</window>
diff --git a/layout/base/crashtests/337066-1.xhtml b/layout/base/crashtests/337066-1.xhtml
new file mode 100644
index 000000000..fadc453f4
--- /dev/null
+++ b/layout/base/crashtests/337066-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function init()
+{
+ var A = document.getElementById("A");
+ var B = document.getElementById("B");
+
+ for (var i = 0; i &lt; 2; ++i)
+ B.insertBefore(document.createElement("span"), A);
+}
+
+</script>
+</head>
+
+<body onload="init()">
+
+<em id="B"><td></td><span id="A"><div></div></span></em>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/337268-1.html b/layout/base/crashtests/337268-1.html
new file mode 100644
index 000000000..ffa7e6599
--- /dev/null
+++ b/layout/base/crashtests/337268-1.html
@@ -0,0 +1,45 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+window.addEventListener("load", foo1, false);
+
+function foo1()
+{
+ document.getElementById("a").style.width = "20em";
+ setTimeout(foo2, 30);
+}
+
+function foo2()
+{
+ document.getElementById("b").style.width = "auto";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<table>
+<tr>
+<td id="a">
+
+<table style="display: -moz-inline-box;">
+<tr>
+<td width="100%">
+
+XXX XXX
+
+<div id="b" style="width: 200%; display: table-column-group;"></div>
+
+</td>
+</tr>
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/337419-1.html b/layout/base/crashtests/337419-1.html
new file mode 100644
index 000000000..58f6a0b31
--- /dev/null
+++ b/layout/base/crashtests/337419-1.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+
+<style>
+#container {
+ -moz-column-count: 3;
+}
+#right {
+ float: right;
+ overflow: hidden;
+}
+</style>
+
+<link rel="alternate" type="application/atom+xml" title="Atom" href="http://weblogs.mozillazine.org/roc/atom.xml" />
+
+</head>
+
+<body>
+
+<div id="container">X<div id="right"></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/337476-1.xul b/layout/base/crashtests/337476-1.xul
new file mode 100644
index 000000000..b04752fc7
--- /dev/null
+++ b/layout/base/crashtests/337476-1.xul
@@ -0,0 +1,32 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+
+<script>
+
+<![CDATA[
+
+window.addEventListener("load", init, false);
+
+function init()
+{
+ document.getElementById("n1").style.display = "table-caption";
+ setTimeout(init2, 30);
+}
+
+function init2()
+{
+ document.getElementById("n2").style.display = "table-caption";
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+
+</script>
+
+
+ <hbox>
+ <vbox flex="1" id="n1"/>
+ <spacer flex="1" id="n2"/>
+ </hbox>
+
+</window>
diff --git a/layout/base/crashtests/338703-1.html b/layout/base/crashtests/338703-1.html
new file mode 100644
index 000000000..54591fc16
--- /dev/null
+++ b/layout/base/crashtests/338703-1.html
@@ -0,0 +1,29 @@
+<html>
+
+<head>
+
+<style id="style"></style>
+<script>
+
+function hmm()
+{
+ document.getElementById("style").textContent = "td { overflow: scroll; } table { background: lightblue; }";
+}
+
+
+</script>
+
+
+
+</head>
+
+
+<body onload="hmm()">
+
+
+
+<table><tr><td>Foopy</td></tr></table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/339651-1.html b/layout/base/crashtests/339651-1.html
new file mode 100644
index 000000000..c7860c388
--- /dev/null
+++ b/layout/base/crashtests/339651-1.html
@@ -0,0 +1,37 @@
+<html style="border: 1px solid red; width: 6em;" class="reftest-wait">
+
+<head>
+<script type="text/javascript">
+
+function f1()
+{
+ document.getElementById("s").style.cssFloat = "left";
+
+ document.body.style.display = "inline";
+ document.getElementById("d").style.display = "inline";
+ document.getElementById("p").style.display = "inline";
+
+ setTimeout(f2, 30);
+}
+
+function f2()
+{
+ document.getElementById("d").style.cssFloat = "left";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="f1()">
+
+TTTTT TTTTT
+<div id="d">
+YY
+<p id="p">
+ZZ
+<span id="s">
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/340093-1.xul b/layout/base/crashtests/340093-1.xul
new file mode 100644
index 000000000..229ca2182
--- /dev/null
+++ b/layout/base/crashtests/340093-1.xul
@@ -0,0 +1,11 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <html:style>
+ menulist, menulist * {
+ overflow: scroll;
+ }
+ </html:style>
+
+ <menulist/>
+
+</window>
diff --git a/layout/base/crashtests/341382-1.html b/layout/base/crashtests/341382-1.html
new file mode 100644
index 000000000..a42e8690f
--- /dev/null
+++ b/layout/base/crashtests/341382-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait"><head>
+<title>Testcase bug 341382 - Crash [@ DoDeletingFrameSubtree] with position:fixed and display: table-caption</title>
+<script>
+function removestyles(i){
+document.getElementById('one').removeAttribute('style');
+document.body.offsetHeight;
+document.getElementById('two').removeAttribute('style');
+document.documentElement.removeAttribute("class");
+}
+
+
+</script></head>
+<body onload="setTimeout(removestyles,0);">
+<span></span>
+<table style="display: table-row-group;">
+<table>
+<span id="one" style="display: table-caption;">
+ <span style="position: fixed;"></span>
+ <div id="two" style="display: table-caption;"></div>
+</span>
+</body>
+</html>
diff --git a/layout/base/crashtests/341382-2.html b/layout/base/crashtests/341382-2.html
new file mode 100644
index 000000000..13216fac4
--- /dev/null
+++ b/layout/base/crashtests/341382-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head><script>
+function removestyles(i){
+document.getElementById('one').removeAttribute('style');
+document.body.offsetHeight;
+document.getElementById('two').removeAttribute('style');
+document.documentElement.removeAttribute("class");
+}
+
+</script></head><body onload="setTimeout(removestyles,0);"><table style="display: table-row-group;"><table><span id="one" style="display: table-caption;"><i style="position: fixed;"></i><div id="two" style="display: table-caption;"></div></span></body></html>
diff --git a/layout/base/crashtests/341858-1.html b/layout/base/crashtests/341858-1.html
new file mode 100644
index 000000000..97c9698e4
--- /dev/null
+++ b/layout/base/crashtests/341858-1.html
@@ -0,0 +1,14 @@
+
+<table style="display: table-caption;">
+<keygen style="display: table-caption;">
+<span style="display: table-caption;">
+<span style="display: table-row-group;">
+
+<body style="display: table-row-group;">
+<input>
+
+
+
+
+
+
diff --git a/layout/base/crashtests/342145-1.xhtml b/layout/base/crashtests/342145-1.xhtml
new file mode 100644
index 000000000..8d87cb818
--- /dev/null
+++ b/layout/base/crashtests/342145-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var img = document.getElementById("img");
+ var t1 = img.childNodes[1]; // a whitespace text node
+ var t2 = document.createTextNode(' ');
+
+ img.insertBefore(t2, t1);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<map name="map" id="map"><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#map" id="img"><area href="http://www.mozilla.org/" shape="rect" coords="0,0,100,100" id="hhh" /> </img></map>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/343293-1.xhtml b/layout/base/crashtests/343293-1.xhtml
new file mode 100644
index 000000000..84da4e1b4
--- /dev/null
+++ b/layout/base/crashtests/343293-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<title>Testcase bug 343293 - Crash [@ nsLayoutUtils::GetFloatFromPlaceholder] using ::first-line, floats, caption and generated content</title>
+<style>
+*::first-line { }
+*::before { content:"--"; }
+</style>
+<script>
+function doe() {
+document.getElementsByTagName('caption')[0].removeAttribute('style');
+document.documentElement.offsetHeight;
+document.getElementsByTagName('span')[0].removeAttribute('style');
+}
+window.onload=doe;
+</script>
+
+<caption style="float: left;"></caption>
+<span style="float: right;"></span>
+This should not crash Mozilla
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/343293-2.xhtml b/layout/base/crashtests/343293-2.xhtml
new file mode 100644
index 000000000..18be6c9ae
--- /dev/null
+++ b/layout/base/crashtests/343293-2.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<style>
+html::first-line { }
+</style>
+<script>
+function doe() {
+ document.getElementsByTagName('caption')[0].removeAttribute('style');
+}
+window.onload=doe;
+</script>
+
+<caption style="float: left;"></caption>
+<span style="float: right;"></span>
+</html>
diff --git a/layout/base/crashtests/343540-1.html b/layout/base/crashtests/343540-1.html
new file mode 100644
index 000000000..fcad37125
--- /dev/null
+++ b/layout/base/crashtests/343540-1.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+
+<script>
+
+function boo()
+{
+ var div = document.getElementById("div");
+ var dd = document.getElementById("dd");
+ var newSpan = document.createElement('span');
+ dd.insertBefore(newSpan, div);
+}
+
+window.addEventListener("load", boo, false);
+</script>
+
+</head>
+
+
+<body>
+
+<dd id="dd"><div id="div"></div></dd>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/344057-1.xhtml b/layout/base/crashtests/344057-1.xhtml
new file mode 100644
index 000000000..74241de46
--- /dev/null
+++ b/layout/base/crashtests/344057-1.xhtml
@@ -0,0 +1,9 @@
+<command xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: table-row;">
+<small xmlns="http://www.w3.org/1999/xhtml" style="float: right;">Ë <semantics xmlns="http://www.w3.org/1998/Math/MathML" style="float: right;">Ë <colgroup xmlns="http://www.w3.org/1999/xhtml" style="float: left;">Ë <s style="display: table-row;">
+<u style="display: table-row;"/>
+<p style="display: table;"/>
+</s>
+</colgroup>
+</semantics>
+</small>
+</command> \ No newline at end of file
diff --git a/layout/base/crashtests/344064-1-inner.xhtml b/layout/base/crashtests/344064-1-inner.xhtml
new file mode 100644
index 000000000..0dd1bbad7
--- /dev/null
+++ b/layout/base/crashtests/344064-1-inner.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+<script>
+
+function removestyles(){
+ var x=document.getElementById('x');
+ x.removeAttribute('style');
+}
+
+setTimeout(removestyles,400);
+
+</script>
+<div><xul:editor id="x" style="float: left;"></xul:editor></div></html> \ No newline at end of file
diff --git a/layout/base/crashtests/344064-1.html b/layout/base/crashtests/344064-1.html
new file mode 100644
index 000000000..c80e1341e
--- /dev/null
+++ b/layout/base/crashtests/344064-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="344064-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/344300-1-inner.xhtml b/layout/base/crashtests/344300-1-inner.xhtml
new file mode 100644
index 000000000..3e980e8c7
--- /dev/null
+++ b/layout/base/crashtests/344300-1-inner.xhtml
@@ -0,0 +1,36 @@
+<hx xmlns="http://www.w3.org/1999/xhtml" style="display: table;">
+<script>
+/*template*/
+var doc = document;
+if (document.getElementById('content'))
+ doc = document.getElementById('content').contentDocument;
+
+function addstyles(){
+var x=doc.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+*::first-line { text-transform: uppercase; background-color:green; font-size:110%;}\
+*::after { content:"anonymous text"; float:right;border:3px solid black;text-transform: uppercase;}\
+*::before { content:"before text"; float:right;border:3px solid black;font-size: 10px;}\
+*::-moz-selection { outline: 2px solid blue;}\
+';
+doc.documentElement.appendChild(x);
+}
+
+function removestyles(i){
+
+
+var x=doc.getElementsByTagName('*');
+
+if (x[i])
+ {
+x[i].removeAttribute('style');
+}
+else { i = 0; }
+ i++;
+setTimeout(removestyles,50,i);
+}
+setTimeout(addstyles,200);
+setTimeout(removestyles,500,0);
+/*template*/
+</script>
+<var style="display: table-column-group;" onmouseover="this.removeAttribute('style')">ý <q style="display: table-footer-group;" onmouseover="this.removeAttribute('style')">ý </q><ins style="display: table-cell;" onmouseover="this.removeAttribute('style')">ý <p style="display: list-item;" onmouseover="this.removeAttribute('style')">ý </p><object style="display: -moz-inline-box;" onmouseover="this.removeAttribute('style')">ý </object></ins></var><table style="display: -moz-inline-box;" onmouseover="this.removeAttribute('style')">ý <ins style="display: -moz-inline-block;" onmouseover="this.removeAttribute('style')">ý </ins></table><body style="display: table-column-group;" onmouseover="this.removeAttribute('style')">ý </body><q style="display: table;" onmouseover="this.removeAttribute('style')">ý </q></hx> \ No newline at end of file
diff --git a/layout/base/crashtests/344300-1.html b/layout/base/crashtests/344300-1.html
new file mode 100644
index 000000000..1c5cb4321
--- /dev/null
+++ b/layout/base/crashtests/344300-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="344300-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/344300-2.html b/layout/base/crashtests/344300-2.html
new file mode 100644
index 000000000..bc447cf73
--- /dev/null
+++ b/layout/base/crashtests/344300-2.html
@@ -0,0 +1,10 @@
+<html><body>
+<object style="display:-moz-deck;">
+<noscript>
+</noscript>
+</object>
+</body></html>
+
+
+
+
diff --git a/layout/base/crashtests/344340-1.xul b/layout/base/crashtests/344340-1.xul
new file mode 100644
index 000000000..8a54f759a
--- /dev/null
+++ b/layout/base/crashtests/344340-1.xul
@@ -0,0 +1,28 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(foopy, 30);" class="reftest-wait">
+
+<script>
+
+<![CDATA[
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function foopy()
+{
+ var hbox = document.createElementNS(XUL_NS, 'hbox');
+ var tooltip = document.createElementNS(XUL_NS, 'tooltip');
+ var vbox = document.createElementNS(XUL_NS, 'vbox');
+ var toolbarspring = document.createElementNS(XUL_NS, 'toolbarspring');
+
+ document.documentElement.appendChild(hbox);
+ hbox.appendChild(toolbarspring);
+
+ vbox.appendChild(tooltip);
+ toolbarspring.appendChild(vbox);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</window>
diff --git a/layout/base/crashtests/347898-1.html b/layout/base/crashtests/347898-1.html
new file mode 100644
index 000000000..d66b5b2e7
--- /dev/null
+++ b/layout/base/crashtests/347898-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<table>
+<ul style="display: table-caption;">
+<keygen style="display: table-caption;">
+
+</td>
+</body>
+</html>
diff --git a/layout/base/crashtests/348126-1-inner.html b/layout/base/crashtests/348126-1-inner.html
new file mode 100644
index 000000000..aafb6c789
--- /dev/null
+++ b/layout/base/crashtests/348126-1-inner.html
@@ -0,0 +1,28 @@
+<html><head><title>Testcase bug 348126 - Crash [@ nsImageFrame::SourceRectToDest] on reload and removing table-caption styles</title>
+
+<script>
+function removestyles(i){
+
+document.getElementsByTagName('table')[0].removeAttribute('style');
+
+document.getElementsByTagName('object')[0].removeAttribute('style');
+
+document.getElementsByTagName('table')[1].removeAttribute('style');
+document.location.reload();
+}
+
+setTimeout(removestyles,500,0);
+</script>
+</head><body>
+Mozilla should not crash on reload on this page<br>
+<object><table style="display: table-caption;">
+<tbody><tr><td></td></tr></tbody>
+</table><object style="display: table-caption;">
+</object><table style="display: table-row-group;">
+<tbody><tr><td>
+<img src="348126-1.gif">
+</td></tr></tbody></table>
+<img src="348126-1.gif">
+</object>
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/348126-1.gif b/layout/base/crashtests/348126-1.gif
new file mode 100644
index 000000000..475ea8c16
--- /dev/null
+++ b/layout/base/crashtests/348126-1.gif
Binary files differ
diff --git a/layout/base/crashtests/348126-1.html b/layout/base/crashtests/348126-1.html
new file mode 100644
index 000000000..2ac1e0da8
--- /dev/null
+++ b/layout/base/crashtests/348126-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="348126-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/348688-1.html b/layout/base/crashtests/348688-1.html
new file mode 100644
index 000000000..363d20fb5
--- /dev/null
+++ b/layout/base/crashtests/348688-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase #1 for bug 348688</title>
+<script>
+function boom() {
+ var e = document.getElementById('inline1');
+ e.parentNode.removeChild(e);
+
+ e = document.getElementById('inline2');
+ e.parentNode.removeChild(e);
+ var x = document.body.offsetHeight;
+}
+</script>
+</head>
+<body onload="boom()">
+
+<div style="overflow:hidden">
+<font><span id="inline1"><b id="float1" style="float:left">x</b></span></font>
+<i id="inline2">y</i>
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/348708-1.xhtml b/layout/base/crashtests/348708-1.xhtml
new file mode 100644
index 000000000..c28cfe778
--- /dev/null
+++ b/layout/base/crashtests/348708-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script>
+function foopy()
+{
+ var optgroup = document.getElementById("optgroup");
+ var newspan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ optgroup.insertBefore(newspan, optgroup.firstChild);
+}
+</script>
+</head>
+
+<body onload="foopy()">
+
+<select><optgroup label="optgroup" id="optgroup"><option>option</option></optgroup></select>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/348729-1-inner.html b/layout/base/crashtests/348729-1-inner.html
new file mode 100644
index 000000000..38f8d615a
--- /dev/null
+++ b/layout/base/crashtests/348729-1-inner.html
@@ -0,0 +1,29 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsRuleNode::GetParentData]</title>
+<script>
+function addstyles1(){
+var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+*::first-letter {float: right; }\
+';
+document.documentElement.appendChild(x);
+
+setTimeout(removestyles,500);
+}
+setTimeout(addstyles1,200);
+
+function removestyles(i){
+document.getElementsByTagName('tfoot')[0].removeAttribute('style');
+document.getElementsByTagName('table')[0].removeAttribute('style');
+
+window.parent.document.documentElement.className = "";
+}
+</script>
+<style>
+*::before { content:"before text";}
+</style>
+</head><body>
+<table style="display: block;">
+<tbody><tr><td></td></tr></tbody><tfoot style="position: absolute;"></tfoot>
+</table>
+</body></html>
diff --git a/layout/base/crashtests/348729-1.html b/layout/base/crashtests/348729-1.html
new file mode 100644
index 000000000..af577b3a0
--- /dev/null
+++ b/layout/base/crashtests/348729-1.html
@@ -0,0 +1,6 @@
+<html class="reftest-wait">
+<head>
+<body>
+<iframe src="348729-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/349095-1.xhtml b/layout/base/crashtests/349095-1.xhtml
new file mode 100644
index 000000000..6d3448376
--- /dev/null
+++ b/layout/base/crashtests/349095-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+body:first-letter { }
+body { display: inline; }
+</style>
+
+<script>
+function foo()
+{
+ document.getElementById("aa").style.display = "block";
+}
+</script>
+
+</head>
+
+<body onload="foo()">
+ <input type="text" style="display: block;" />
+ <span>Z</span>
+ <span id="aa">A</span>
+ <span style="display: block;">B</span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/350128-1.xhtml b/layout/base/crashtests/350128-1.xhtml
new file mode 100644
index 000000000..1e4ff70fe
--- /dev/null
+++ b/layout/base/crashtests/350128-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="lub4">
+ <content>
+ <html:span style="color: green;">
+ <children/>
+ </html:span>
+ </content>
+ </binding>
+</bindings>
+
+
+</head>
+
+<body onload="document.getElementById('gogo');">
+ <span style="-moz-binding: url('#lub4')"><div/><em id="gogo">I</em></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/350267-1.html b/layout/base/crashtests/350267-1.html
new file mode 100644
index 000000000..f6e5f8669
--- /dev/null
+++ b/layout/base/crashtests/350267-1.html
@@ -0,0 +1,2 @@
+<samp style="display: -moz-inline-block;">
+<object style="display: block;"> \ No newline at end of file
diff --git a/layout/base/crashtests/354133-1-inner.xhtml b/layout/base/crashtests/354133-1-inner.xhtml
new file mode 100644
index 000000000..8003a3e99
--- /dev/null
+++ b/layout/base/crashtests/354133-1-inner.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<title>Testcase bug 354133 - Crash [@ nsBlockBandData::Init] with unminimised stirdom mathml/xul testcase</title>
+</head>
+<body>
+This page should not crash Mozilla
+<xul:scrollbar>
+<mathml:ms id="a">
+<mathml:sinh>
+<xul:box id="b"/>
+</mathml:sinh>
+</mathml:ms>
+</xul:scrollbar>
+
+<html:script>
+function stirdom(){
+document.getElementById('a').appendChild(document.getElementById('b'));
+}
+setTimeout(stirdom,200);
+</html:script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/354133-1.html b/layout/base/crashtests/354133-1.html
new file mode 100644
index 000000000..acb6e4a83
--- /dev/null
+++ b/layout/base/crashtests/354133-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="354133-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/354766-1.xhtml b/layout/base/crashtests/354766-1.xhtml
new file mode 100644
index 000000000..bb491036e
--- /dev/null
+++ b/layout/base/crashtests/354766-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+</head>
+
+<body>
+
+<table style="border-collapse: collapse;">
+ <tbody>
+ <tr>
+ <td><mtd xmlns="http://www.w3.org/1998/Math/MathML"/></td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+
+</html>
+
diff --git a/layout/base/crashtests/354771-1.xul b/layout/base/crashtests/354771-1.xul
new file mode 100644
index 000000000..0ff2ba8e7
--- /dev/null
+++ b/layout/base/crashtests/354771-1.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<listbox flex="1" style="float: right">
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="foo"/>
+ <listitem label="b" style="float: left;"/>
+ <listitem label="c" style="position: absolute"/>
+</listbox>
+
+</window>
diff --git a/layout/base/crashtests/355989-1.xhtml b/layout/base/crashtests/355989-1.xhtml
new file mode 100644
index 000000000..1af1c3273
--- /dev/null
+++ b/layout/base/crashtests/355989-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+ body, #tq { display: inline; }
+ #tq { position: relative; }
+</style>
+
+<style id="newstyle">
+</style>
+
+<script>
+function foo()
+{
+ document.getElementById("tq").style.position = "static";
+ document.getElementById("newstyle").textContent = "*:first-letter { }";
+}
+</script>
+
+</head>
+
+<body onload="foo()">
+ <table><tr><td>Table</td></tr></table>
+ <div id="tq">Div</div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/355993-1.xhtml b/layout/base/crashtests/355993-1.xhtml
new file mode 100644
index 000000000..e902ee550
--- /dev/null
+++ b/layout/base/crashtests/355993-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<style>
+body, body * { position: fixed; }
+</style>
+</head>
+
+<body>
+
+
+<div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+
+ <mtable>
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/356325-1.xul b/layout/base/crashtests/356325-1.xul
new file mode 100644
index 000000000..c139e8f05
--- /dev/null
+++ b/layout/base/crashtests/356325-1.xul
@@ -0,0 +1,20 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML"
+title="Testcase bug 356325 - Crash [@ nsCSSFrameConstructor::FindFrameWithContent] with tooltip, mathml:and and moving stuff in it">
+<description value="This page should not crash Mozilla"/>
+<box id="y">
+ <box id="d"/>
+</box>
+<tooltip>
+ <mathml:and id="x"/>
+</tooltip>
+
+<html:script>
+function doe() {
+document.getElementById('x').appendChild(document.getElementById('y'));
+document.getElementById('y').appendChild(document.getElementById('d'));
+}
+window.onload=doe;
+</html:script>
+
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/358729-1.xhtml b/layout/base/crashtests/358729-1.xhtml
new file mode 100644
index 000000000..b9a3cc35f
--- /dev/null
+++ b/layout/base/crashtests/358729-1.xhtml
@@ -0,0 +1,52 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+<![CDATA[
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function foo()
+{
+ var DIVa = document.getElementById('a');
+
+ var DIVb = document.createElementNS(HTML_NS, 'div');
+ DIVb.appendChild(document.createTextNode('DIVb'));
+ DIVa.appendChild(DIVb);
+
+ document.body.offsetHeight;
+
+ var DIVc = document.createElementNS(HTML_NS, 'div');
+ DIVc.appendChild(document.createTextNode('DIVc'));
+ DIVb.appendChild(DIVc);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(foo, 30)">
+
+<div>
+
+<table border="1">
+ <tr>
+ <td>
+ <span dir="ltr">
+ span
+ <th>
+ <div id="a"></div>
+ </th>
+ </span>
+ </td>
+ </tr>
+</table>
+
+<div><span dir="rtl">RTL</span></div>
+</div>
+
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/360339-1.xul b/layout/base/crashtests/360339-1.xul
new file mode 100644
index 000000000..b71973f19
--- /dev/null
+++ b/layout/base/crashtests/360339-1.xul
@@ -0,0 +1,16 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<html:style>
+* { float: right; }
+</html:style>
+
+ <menulist>
+ <menupopup id="ping">
+ </menupopup>
+ </menulist>
+
+</window>
diff --git a/layout/base/crashtests/360339-2.xul b/layout/base/crashtests/360339-2.xul
new file mode 100644
index 000000000..2050cf6fb
--- /dev/null
+++ b/layout/base/crashtests/360339-2.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<html:style>
+* { float: right; }
+#ping { float: none; }
+</html:style>
+
+<hbox>
+ <menulist>
+ <menupopup id="ping">
+ <menuitem label="1"/>
+ </menupopup>
+ </menulist>
+</hbox>
+
+</window>
diff --git a/layout/base/crashtests/363729-1.html b/layout/base/crashtests/363729-1.html
new file mode 100644
index 000000000..1955869fe
--- /dev/null
+++ b/layout/base/crashtests/363729-1.html
@@ -0,0 +1,3 @@
+<html class="reftest-print">
+<body>
+<b onfocus="event.target.setAttribute('tabindex', Math.floor(Math.random()*5)-9)"display: inline-table;position: fixed;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: always;page-break-inside: inherit; style="display: inline-table;position: fixed;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: always;page-break-inside: inherit;"><sup rowspan="12"display: table-caption;position: static;overflow: -moz-hidden-unscrollable;float: auto;direction: ltr;page-break-before: avoid;page-break-after: right;page-break-inside: inherit; style="display: table-caption;position: static;overflow: -moz-hidden-unscrollable;float: auto;direction: ltr;page-break-before: avoid;page-break-after: right;page-break-inside: inherit;"><bdo onfocus="event.target.parentNode.removeChild(event.target)"display: table-footer-group;position: absolute;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: right;page-break-inside: auto; style="display: table-footer-group;position: absolute;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: right;page-break-inside: auto;"><dir tabindex="12"display: -moz-grid;position: static;overflow: auto;float: left;direction: ltr;page-break-before: avoid;page-break-after: inherit;page-break-inside: inherit; style="display: -moz-grid;position: static;overflow: auto;float: left;direction: ltr;page-break-before: avoid;page-break-after: inherit;page-break-inside: inherit;"><i rowspan="1"display: -moz-stack;position: fixed;overflow: visible;float: right;direction: rtl;page-break-before: right;page-break-after: always;page-break-inside: avoid; style="display: -moz-stack;position: fixed;overflow: visible;float: right;direction: rtl;page-break-before: right;page-break-after: always;page-break-inside: avoid;"><select colspan="1"display: block;position: absolute;overflow: hidden;float: right;direction: auto;page-break-before: auto;page-break-after: avoid;page-break-inside: auto; style="display: block;position: absolute;overflow: hidden;float: right;direction: auto;page-break-before: auto;page-break-after: avoid;page-break-inside: auto;"></abbr></var></u></base></em></button></optgroup></menu></body>
diff --git a/layout/base/crashtests/363729-2.html b/layout/base/crashtests/363729-2.html
new file mode 100644
index 000000000..57389012c
--- /dev/null
+++ b/layout/base/crashtests/363729-2.html
@@ -0,0 +1,18 @@
+<html class="reftest-print">
+<head>
+<title>Testcase Bug 363729 – Crash [@ nsIFrame::GetPositionIgnoringScrolling] on print preview that uses position: fixed</title>
+</head>
+<body>
+This page should not crash on print preview
+<span style="position: fixed; page-break-after: always;"></span>
+<dir>
+<span style="display: inline-table; position: fixed; page-break-after: always;">
+
+<span style="position: absolute;">
+<span style=" position: fixed;"></span>
+</span>
+
+</span>
+</dir>
+</body>
+</html>
diff --git a/layout/base/crashtests/363729-3.html b/layout/base/crashtests/363729-3.html
new file mode 100644
index 000000000..05d4e2905
--- /dev/null
+++ b/layout/base/crashtests/363729-3.html
@@ -0,0 +1,20 @@
+<html class="reftest-print">
+<head>
+<title>Testcase Bug 363729 – Crash [@ nsIFrame::GetPositionIgnoringScrolling] on print preview that uses position: fixed (Branch version)</title>
+</head>
+<body>
+This page should not crash on print preview
+<span style="page-break-after: always;"></span>
+
+<dir>
+ <table style="position: fixed; page-break-after: always;">
+ <tr><td>
+ <span style="position: absolute;">
+ <span style=" position: fixed;"></span>
+ </span>
+ </td></tr>
+ </table>
+</dir>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/364427-1.html b/layout/base/crashtests/364427-1.html
new file mode 100644
index 000000000..04cbeacf1
--- /dev/null
+++ b/layout/base/crashtests/364427-1.html
@@ -0,0 +1,34 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var d = document.getElementById("d");
+ d.parentNode.removeChild(d);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div id="d">
+ <div style="float: right">
+ <table border="1">
+ <tr>
+ <td style="display: -moz-groupbox"><img></td>
+ </tr>
+ <tr style="position: absolute">
+ <td>TD2</td>
+ </tr>
+ </table>
+ </div>
+</div>
+
+</body>
+
+</html>
+
diff --git a/layout/base/crashtests/365909-1.xhtml b/layout/base/crashtests/365909-1.xhtml
new file mode 100644
index 000000000..e543f0927
--- /dev/null
+++ b/layout/base/crashtests/365909-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head></head>
+
+<body onload="document.getElementById('tbody').appendChild(document.createTextNode('Bar'));">
+ <p>Reload to see the assertion failure.</p>
+ <div><span dir="rtl">Foo<tbody id="tbody"></tbody></span></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/365909-2.xhtml b/layout/base/crashtests/365909-2.xhtml
new file mode 100644
index 000000000..73ffa4a34
--- /dev/null
+++ b/layout/base/crashtests/365909-2.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head></head>
+
+<body onload="document.getElementById('td').appendChild(document.createTextNode('Bar'));">
+ <p>Reload to see the assertion failure.</p>
+ <div><span dir="rtl">Foo<td id="td"></td></span></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/366128-1.xhtml b/layout/base/crashtests/366128-1.xhtml
new file mode 100644
index 000000000..66c985c5a
--- /dev/null
+++ b/layout/base/crashtests/366128-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var doomedOption = document.getElementById("doomedOption");
+ var floated = document.getElementById("floated");
+
+ doomedOption.parentNode.removeChild(doomedOption);
+ floated.removeAttributeNS(null, "style");
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+ <select>
+ <xul:label>
+ <option id="doomedOption">M</option>
+ <span id="floated" style="float: right;"/>
+ </xul:label>
+ </select>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/366271-1-frame.svg b/layout/base/crashtests/366271-1-frame.svg
new file mode 100644
index 000000000..8ba0dc599
--- /dev/null
+++ b/layout/base/crashtests/366271-1-frame.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(function() { document.getElementById('focc').style.overflow = 'scroll'; setTimeout(function() { location.reload(); }, 200); }, 200);">
+
+ <g id="focc">
+ <foreignObject width="500" height="500" id="fo" x="20" y="20">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <select><option>Reload to see some assertions</option></select>
+ </div>
+ </foreignObject>
+ </g>
+
+</svg>
diff --git a/layout/base/crashtests/366271-1.html b/layout/base/crashtests/366271-1.html
new file mode 100644
index 000000000..eb89acfd9
--- /dev/null
+++ b/layout/base/crashtests/366271-1.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var childLoads = 0;
+function inc()
+{
+ ++childLoads;
+ if (childLoads >= 2)
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<iframe src="366271-1-frame.svg" onload="inc();"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/366967-1.html b/layout/base/crashtests/366967-1.html
new file mode 100644
index 000000000..f8e63d96f
--- /dev/null
+++ b/layout/base/crashtests/366967-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html class="reftest-wait">
+<head>
+
+<style>
+#cat { float: left; }
+#zebra { background: lightgreen; }
+#zebra:after { content: "a b c d e"; }
+#zebra:first-letter { display: none; }
+</style>
+
+<script>
+function boom1()
+{
+ document.getElementById("cat").style.outline = "1px solid yellow";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("cat").style.overflow = "auto";
+ document.documentElement.removeAttribute("class")
+}
+</script>
+
+</head>
+
+<body onload="setTimeout(boom1, 30)" style="overflow: scroll">
+ <div id="zebra"><b id="cat">Cat</b></div>
+ <div style="direction: rtl">This is an RTL div</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/367015-1.html b/layout/base/crashtests/367015-1.html
new file mode 100644
index 000000000..d1fe1c5f6
--- /dev/null
+++ b/layout/base/crashtests/367015-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+
+<style>
+html:first-line { }
+body { direction: rtl; float: right; }
+</style>
+
+<script>
+function boom()
+{
+ document.body.style.cssFloat = "none";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+<p>Hello world</p>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/367243-1.html b/layout/base/crashtests/367243-1.html
new file mode 100644
index 000000000..23910438b
--- /dev/null
+++ b/layout/base/crashtests/367243-1.html
@@ -0,0 +1,37 @@
+<html class="reftest-wait">
+<head>
+
+<style id="style">
+.ch1 { counter-increment: chicken; }
+</style>
+
+<script>
+function boom()
+{
+ document.getElementsByTagName("ol")[0].setAttribute("class", "wtf");
+ document.getElementById("style").textContent = ".ch2 { counter-increment: chicken; }";
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+
+<ol>
+ <li class="ch1">item</li>
+ <li>item
+ <ol>
+ <li class="ch2">item</li>
+ </ol>
+ </li>
+</ol>
+
+<ol class="ch2">
+ <li>item</li>
+</ol>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/367498-1.html b/layout/base/crashtests/367498-1.html
new file mode 100644
index 000000000..5c4ef4da1
--- /dev/null
+++ b/layout/base/crashtests/367498-1.html
@@ -0,0 +1,8 @@
+<html><head>
+</head><body>
+This page should not have a very large height;
+<span style="display: -moz-grid-line;">
+<select></select>
+</span>
+</body>
+</html>
diff --git a/layout/base/crashtests/367498-2.html b/layout/base/crashtests/367498-2.html
new file mode 100644
index 000000000..2c85ac0a4
--- /dev/null
+++ b/layout/base/crashtests/367498-2.html
@@ -0,0 +1,14 @@
+<html><head>
+</head><body>
+<marquee>
+<div style="border: 1px solid black; -moz-border-radius: 2em;">
+<marquee>
+<span style="display: -moz-grid-line;">
+<select></select>
+</span>
+</marquee>
+</div>
+</marquee>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/369176-1.html b/layout/base/crashtests/369176-1.html
new file mode 100644
index 000000000..536206c46
--- /dev/null
+++ b/layout/base/crashtests/369176-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+
+<script>
+
+function boom()
+{
+ document.getElementById("f").className = 'q';
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<style>
+
+body {
+ width: 10em;
+}
+
+#f:after {
+ content: "TTT";
+}
+
+</style>
+
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<span id="f">foo foo foo foo foo foo foo foo foo foo<span style="display: block"></span></span>
+
+
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/369547-1.html b/layout/base/crashtests/369547-1.html
new file mode 100644
index 000000000..6820cfc36
--- /dev/null
+++ b/layout/base/crashtests/369547-1.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsSubDocumentFrame::Reflow] with testcase, using first-letter, first-line, inline-block and iframes</title>
+</head>
+<body>
+<div style="width:1440px;" id="a">
+<div>
+<fieldset>
+
+<legend style="display: inline-block;"></legend>
+<span></span>
+<iframe></iframe>
+<iframe></iframe>
+<legend style="display: list-item;">
+<iframe></iframe>
+</legend>
+</fieldset>
+</div>
+</div>
+<script>
+function addfirstline(){
+var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+#a *::first-letter { }\
+#a *::first-line {}\
+#a *::after { content:"anonymous text"; text-transform: uppercase;height: 90%;}\
+#a *::before { content:"before text"; font-size: 10px;}\
+';
+document.documentElement.appendChild(x);
+}
+setTimeout(addfirstline,200);
+
+var j=0;
+function replacestyles(i){
+var x=document.getElementById('a').getElementsByTagName('*');
+if (j>=2) return;
+if (x[i] && x[i+1])
+ {
+var temp = x[i+1].getAttribute('style');
+x[i+1].setAttribute('style', x[i].getAttribute('style'));
+x[i].setAttribute('style', temp);
+}
+else { i = 0;j++;}
+ i++;
+setTimeout(replacestyles,50,i);
+}
+setTimeout(replacestyles,500,0);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/369547-2.html b/layout/base/crashtests/369547-2.html
new file mode 100644
index 000000000..d3e7f2758
--- /dev/null
+++ b/layout/base/crashtests/369547-2.html
@@ -0,0 +1,15 @@
+<html><head><script>
+function doe2() {
+document.getElementById('a').setAttribute('style', 'display: inline-block;');
+document.body.offsetHeight;
+document.getElementById('b').removeAttribute('style');
+document.body.offsetHeight;
+}
+setTimeout(doe2,200,0);
+</script>
+</head>
+<body style="display: -moz-inline-box;"><span style="display: inline-block;"><span style="display: inline-block;"></span></span><span id="a">
+<iframe></iframe>
+<div id="b" style="display: table-footer-group;"></div>
+</span></body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/369945-1.xhtml b/layout/base/crashtests/369945-1.xhtml
new file mode 100644
index 000000000..24d07f9d3
--- /dev/null
+++ b/layout/base/crashtests/369945-1.xhtml
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ z = document.getElementById("z");
+ p = z.parentNode;
+ p.appendChild(z);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 10)">
+
+<p>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <mi>x
+ <xul:scrollbar>
+ <xul:hbox>
+ <xul:hbox id="z">
+ <mfrac>
+ <mn>1</mn>
+ <mn>2</mn>
+ </mfrac>
+ </xul:hbox>
+ </xul:hbox>
+ </xul:scrollbar>
+ </mi>
+ </math>
+</p>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/371681-1.xhtml b/layout/base/crashtests/371681-1.xhtml
new file mode 100644
index 000000000..4f3b95653
--- /dev/null
+++ b/layout/base/crashtests/371681-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+<script>
+function boom()
+{
+ var y = document.getElementById("y");
+ y.parentNode.removeChild(y);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+<body onload="setTimeout(boom, 30);">
+
+<div style="float: left">X<xul:hbox><input type="radio"/></xul:hbox></div>
+<div id="y" style="float: left">Y</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/372237-1.html b/layout/base/crashtests/372237-1.html
new file mode 100644
index 000000000..84301461f
--- /dev/null
+++ b/layout/base/crashtests/372237-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("b").style.overflow = "hidden";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("g").style.display = "none";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div style="float: left;">
+<div id="b" style="display: -moz-box; border: 1px solid black;"><img width="16" height="16" src="../../../testing/crashtest/images/tree.gif"/></div>
+<div style="position: fixed;"></div>
+</div>
+
+<div id="g" style="display: inline"><div></div></div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/372475-1.xhtml b/layout/base/crashtests/372475-1.xhtml
new file mode 100644
index 000000000..ec3d75463
--- /dev/null
+++ b/layout/base/crashtests/372475-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<body>
+
+<div style="display: -moz-popup"></div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/372550-1.html b/layout/base/crashtests/372550-1.html
new file mode 100644
index 000000000..a1dd8ea5b
--- /dev/null
+++ b/layout/base/crashtests/372550-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ div#x::first-letter { color: blue; }
+ </style>
+</head>
+<body>
+<div id="x">x</div>
+<script>
+ document.body.offsetWidth;
+ var div = document.getElementById("x");
+ div.id = "y";
+ div.removeChild(div.firstChild);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/372576.xul b/layout/base/crashtests/372576.xul
new file mode 100644
index 000000000..2b72ceb8d
--- /dev/null
+++ b/layout/base/crashtests/372576.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window id="yourwindow" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script type="text/javascript">
+<![CDATA[
+// put some js code here
+]]>
+</script>
+<toolbox>
+ <toolbar>
+ <toolbarbutton type="menu" class="toolbarbutton-1 firefly_files" label="Crash" tooltiptext="Crash">
+ <menupopup onpopupshown="this.enableKeyboardNavigator(false);">
+ <menuitem>
+ <textbox type="autocomplete" oninput="doFilter(this.value,false)" onchange="doFilter(this.value,false)" />
+ </menuitem>
+ </menupopup>
+ </toolbarbutton>
+ </toolbar>
+ </toolbox>
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/373628-1.html b/layout/base/crashtests/373628-1.html
new file mode 100644
index 000000000..2ce99cdfc
--- /dev/null
+++ b/layout/base/crashtests/373628-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 373628</title>
+<script>
+function stop() {
+ document.body.removeChild(document.body.children[0]);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload='setTimeout(stop, 1000)'>
+
+<iframe src="373628.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/373628.html b/layout/base/crashtests/373628.html
new file mode 100644
index 000000000..c18cea6f9
--- /dev/null
+++ b/layout/base/crashtests/373628.html
@@ -0,0 +1,933 @@
+<html>
+<head>
+<script>
+function doe() {
+window.location.reload();
+}
+</script>
+</head>
+
+<body style=" display: table-cell; direction: ltr;" onload="setTimeout(doe, 0);">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<nobr style=" display: -moz-groupbox; direction: rtl;">
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: -moz-grid-group; position: absolute; direction: rtl;">
+</s>
+</q>
+<pre style=" display: -moz-inline-stack; position: absolute; direction: ltr;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-grid-group; position: absolute; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</q>
+<ol style=" display: block; direction: rtl;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-grid-group; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+</q>
+</s>
+</q>
+
+</ol>
+</pre>
+</nobr>
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-grid-group; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+<s style=" display: -moz-box; position: absolute; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</q>
+</s>
+</q>
+</s>
+</q>
+<p style=" display: table; position: absolute; direction: ltr;">
+</p>
+<pre style=" display: inline; position: absolute; float: left; direction: ltr;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-grid-group; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+<s style=" display: -moz-box; position: absolute; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<q style=" display: -moz-deck; position: absolute; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</s>
+<map style=" display: inline-table; position: fixed; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-deck; position: absolute; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</map>
+</q>
+</s>
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<samp style=" display: inherit; position: fixed;">
+</samp>
+<p>
+</p>
+<p style=" display: -moz-grid; direction: rtl;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<bdo style=" display: -moz-inline-stack; direction: ltr;">
+</bdo>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<p style=" display: -moz-popup; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<bdo style=" display: -moz-inline-stack; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+<q style=" display: -moz-grid-group; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<ol style=" display: inline-block; float: left; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<nobr style=" display: -moz-stack; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: table-column; position: absolute; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+<p>
+</p>
+<bdo style=" display: table-column; position: absolute; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</bdo>
+</bdo>
+</nobr>
+<samp style=" display: table-cell; position: absolute; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<small style=" display: -moz-stack; position: fixed;">
+<nobr style=" display: table-footer-group; direction: ltr;">
+<samp style=" display: table-row-group; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</nobr>
+</small>
+<samp style=" display: -moz-inline-stack; direction: rtl;">
+<bdo style=" display: -moz-grid-line;">
+<samp style=" display: table-row; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</bdo>
+</samp>
+</s>
+</bdo>
+</bdo>
+</samp>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<pre style=" display: table-row; direction: ltr;">
+<samp style=" display: -moz-inline-box; direction: rtl;">
+<map style=" display: -moz-grid-line; position: fixed; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</map>
+</samp>
+</pre>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m m
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p style=" display: table-caption; ">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<s style=" display: -moz-stack; position: fixed; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<s style=" display: -moz-stack; position: fixed; direction: rtl;">
+<s style=" display: -moz-inline-grid; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<s style=" display: -moz-stack; position: fixed; direction: rtl;">
+<s style=" display: -moz-inline-grid; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<bdo style="overflow: hidden; display: -moz-popup; ">
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<ol style=" display: -moz-inline-stack; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<s style=" display: -moz-stack; position: fixed; direction: rtl;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+</q>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<listing style=" display: -moz-deck; direction: ltr;">
+<p style=" display: block; position: fixed; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<s style=" display: -moz-stack; position: fixed; direction: rtl;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</s>
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+</listing>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</ol>
+</ol>
+</pre>
+<listing style=" display: -moz-inline-box;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</listing>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<samp style=" display: inherit; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<nobr style=" display: -moz-inline-grid; position: absolute; direction: ltr;">
+<ol style=" display: table-caption; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<map style=" display: list-item; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<listing style=" display: -moz-inline-grid; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style="overflow: auto; display: inline-table; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: table-column-group; direction: rtl;">
+<samp style="display: inline; " display:="" inline-table;position:="" fixed;overflow:="" right;direction:="" ltr;="">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</s>
+</s>
+</listing>
+</ol>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</s>
+</s>
+<p>
+</p>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<small style=" display: -moz-inline-grid; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+
+
+
+</small>
+</bdo>
+</s>
+</s>
+</nobr>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-grid; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</small>
+</bdo>
+</s>
+</s>
+</samp>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<ol style=" display: block; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-grid; position: fixed; direction: ltr;">
+<q style=" display: table-row; direction: rtl;">
+<samp style=" display: -moz-stack; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</q>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p style=" display: -moz-inline-stack; direction: rtl;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-grid; position: fixed; direction: ltr;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-grid; position: fixed; direction: ltr;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: -moz-grid-line; position: fixed;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</body>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>
+<ol style="display: inline-block;direction: ltr;">
+<p style="display: inherit;position: fixed;direction: ltr;">
+<body style="display: -moz-deck;position: absolute;direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</ol>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</html>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</body>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</p>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</pre>
+<legend style="display: -moz-groupbox;position: fixed;direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</ol>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<pre style=" display: table-cell; position: absolute;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</q>
+<nobr style=" display: inherit; position: fixed; direction: rtl;">
+<q style=" display: -moz-grid; direction: rtl;">
+<s style=" display: table; position: absolute; direction: ltr;">
+<q style=" display: -moz-grid-line; position: absolute;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<s style=" display: inherit; position: fixed; direction: rtl;">
+<small style=" display: -moz-grid-line;">
+</small>
+</s>
+</q>
+<ol style=" display: -moz-grid; direction: rtl;">
+<pre style=" display: table-column-group; position: absolute;">
+<q style=" display: -moz-grid; direction: rtl;">
+<s style=" display: inherit; position: fixed; direction: rtl;">
+<small style=" display: -moz-grid-line;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</small>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<samp style=" display: table-column-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</q>
+</pre>
+</ol>
+<q style=" display: -moz-grid; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</q>
+<p>
+</p>
+<q style=" display: -moz-grid; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<samp style=" display: inline-block; ">
+</samp>
+</bdo>
+</q>
+<p style=" display: -moz-grid-group; position: absolute; direction: rtl;">
+<q style=" display: -moz-grid; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</bdo>
+</q>
+</p>
+</nobr>
+</listing>
+</listing>
+</nobr>
+</small>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</s>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</pre>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-grid-group; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-groupbox; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-grid-line;">
+<bdo style="overflow: scroll; display: -moz-grid; float: right;">
+<bdo style="overflow: hidden; display: -moz-popup; ">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+</body>
+</html>
diff --git a/layout/base/crashtests/373919.xhtml b/layout/base/crashtests/373919.xhtml
new file mode 100644
index 000000000..42b194b9e
--- /dev/null
+++ b/layout/base/crashtests/373919.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<object id="mw_ij" xmlns="http://www.w3.org/1999/xhtml" style="display: none;"/>
+
+<textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <dir xmlns="http://www.w3.org/1999/xhtml" style="overflow: scroll;position: fixed;"/>
+</textnode>
+
+<wizardpage id="mw_ab" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <label id="mw_kl">
+ <toolbox style="float: right;"/>
+ </label>
+</wizardpage>
+
+<listbox id="mw_cd" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe() {
+document.getElementById('mw_ab').insertBefore(document.getElementById('mw_cd'), document.getElementById('mw_ab').childNodes[0]);
+document.documentElement.offsetHeight;
+document.getElementById('mw_ij').appendChild(document.getElementById('mw_kl'));
+document.documentElement.offsetHeight;
+}
+setTimeout(doe, 100);
+
+setTimeout(function() {window.location=window.location;}, 500);
+</script>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/374193-1.xhtml b/layout/base/crashtests/374193-1.xhtml
new file mode 100644
index 000000000..b567f97c0
--- /dev/null
+++ b/layout/base/crashtests/374193-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ ><mtd xmlns="http://www.w3.org/1998/Math/MathML"
+ ><th xmlns="http://www.w3.org/1999/xhtml"
+ /><mtable xmlns="http://www.w3.org/1998/Math/MathML"
+ ><th xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(374193-1xbl.xml);" id="mw_th20"></th></mtable></mtd><style>
+mtable::after { content:"anonymous text"; }
+</style></html>
diff --git a/layout/base/crashtests/374193-1xbl.xml b/layout/base/crashtests/374193-1xbl.xml
new file mode 100644
index 000000000..c1d288352
--- /dev/null
+++ b/layout/base/crashtests/374193-1xbl.xml
@@ -0,0 +1,10 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<implementation>
+<constructor>
+ this.style.position='fixed';
+</constructor>
+</implementation>
+<content><children/></content>
+</binding>
+</bindings>
diff --git a/layout/base/crashtests/374297-1.html b/layout/base/crashtests/374297-1.html
new file mode 100644
index 000000000..6ff2bc3f4
--- /dev/null
+++ b/layout/base/crashtests/374297-1.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom() {
+ var newNode = document.createElementNS("http://www.w3.org/1999/xhtml", 'table');
+ document.getElementById('td').appendChild(newNode);
+ document.getElementById('table2').setAttribute('align', 'right');
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(boom,30)">
+
+<table id="table2"><tr><td id="td"></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/374297-2.html b/layout/base/crashtests/374297-2.html
new file mode 100644
index 000000000..86aeae8ce
--- /dev/null
+++ b/layout/base/crashtests/374297-2.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom() {
+ var $table2 = document.getElementById('table2');
+ $table2.setAttribute('width', '30%');
+ var $th273 = document.getElementById('th273');
+ $th273.style.position = "relative";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom,30)">
+
+<table id="table2"><tr><div><th id="th273"></th></div></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/376223-1.xhtml b/layout/base/crashtests/376223-1.xhtml
new file mode 100644
index 000000000..91d72b1ff
--- /dev/null
+++ b/layout/base/crashtests/376223-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+
+function boom()
+{
+ var listbox = document.getElementById("listbox");
+ var td = document.getElementById("td");
+
+ var listitem = document.createElementNS(XUL_NS, "listitem");
+
+ listbox.appendChild(listitem);
+ listbox.appendChild(td);
+}
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table><tbody><tr><td id="td">X</td></tr></tbody></table>
+
+<xul:listbox id="listbox"/>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/378325-1.html b/layout/base/crashtests/378325-1.html
new file mode 100644
index 000000000..37426875a
--- /dev/null
+++ b/layout/base/crashtests/378325-1.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+<head>
+<title>Testcase bug - Crash [@ PresShell::FlushPendingNotifications] when removing window on focus and then reappearing again</title>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+</head>
+<body>
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Cbody%20tabindex%3D%221%22%20onfocus%3D%22top.doe2%28%29%3Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%22%3E%0A%3Cscript%3E%0AsetTimeout%28function%28%29%7Bdocument.body.focus%28%29%7D%2C%20200%29%3B%0A%3C/script%3E%3C/body%3E%3C/html%3E" id="content"></iframe>
+
+<script>
+function doe() {
+ if (!document.getElementById('content')) {
+ var y = document.createElement('iframe');
+ y.id = 'content';
+ y.src = 'data:text/html;charset=utf-8,%3Chtml%3E%3Cbody%20tabindex%3D%221%22%20onfocus%3D%22top.doe2%28%29%3Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%22%3E%0A%3Cscript%3E%0AsetTimeout%28function%28%29%7Bdocument.body.focus%28%29%7D%2C%20200%29%3B%0A%3C/script%3E%3C/body%3E%3C/html%3E';
+ document.body.appendChild(y);
+ }
+}
+
+ function doe2() {
+ setInterval(doe, 200);
+ }
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/378682.html b/layout/base/crashtests/378682.html
new file mode 100644
index 000000000..2f4bf8dc7
--- /dev/null
+++ b/layout/base/crashtests/378682.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsPresContext::GetContainerInternal] when removing window on focus and reloading</title>
+</head>
+<body>
+This page should not crash Mozilla
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%3E%0A%3Ciframe%3E%3C/iframe%3E%0A%3Cscript%3E%0Awindow.frames%5B0%5D.focus%28%29%3B%0AsetTimeout%28doe%2C%20200%29%3B%0Afunction%20doe%28%29%20%7B%0Awindow.frames%5B0%5D.location.reload%28%29%3B%0A%7D%0Afunction%20doe2%28%29%20%7B%0Awindow.addEventListener%28%27focus%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0A%7D%0AsetTimeout%28doe2%2C%2050%29%3B%0A%3C/script%3E%0A%3C/body%3E%0A%3C/html%3E"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/379105-1.xhtml b/layout/base/crashtests/379105-1.xhtml
new file mode 100644
index 000000000..e20cced16
--- /dev/null
+++ b/layout/base/crashtests/379105-1.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<bindings xmlns="http://www.mozilla.org/xbl">
+
+<binding id="x"><content>
+ <zzz><children/></zzz>
+</content></binding>
+
+<binding id="empty"><content>
+</content></binding>
+
+</bindings>
+
+<script>
+<![CDATA[
+
+var xbltarget;
+
+function boom1()
+{
+ xbltarget = document.getElementById("xbltarget");
+ xbltarget.style.MozBinding = "url('#x')";
+ setTimeout(boom2, 0);
+}
+
+function boom2()
+{
+ var nodes = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(xbltarget));
+ if (!nodes) {
+ setTimeout(boom2, 10);
+ return;
+ }
+ var anox = nodes[0];
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "frame")
+ frame.src = "data:text/html,<html><body>Hi!</body></html>";
+ anox.appendChild(frame);
+ xbltarget.style.MozBinding = "url('#empty')";
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+<body onload="boom1()">
+<div id="xbltarget"></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/379419-1.xhtml b/layout/base/crashtests/379419-1.xhtml
new file mode 100644
index 000000000..406876160
--- /dev/null
+++ b/layout/base/crashtests/379419-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<table border="1">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ <thead style="display: block;"></thead>
+</table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/379768-1.html b/layout/base/crashtests/379768-1.html
new file mode 100644
index 000000000..94f66100c
--- /dev/null
+++ b/layout/base/crashtests/379768-1.html
@@ -0,0 +1,11 @@
+<html>
+ <body>
+ <table>
+ <tr style="display: -moz-groupbox;">
+ <td style="float: right;">
+ <img width="10" height="10" style="position: fixed;">
+ </td>
+ </tr>
+ </table>
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/379799-1.html b/layout/base/crashtests/379799-1.html
new file mode 100644
index 000000000..314744f78
--- /dev/null
+++ b/layout/base/crashtests/379799-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style id="firstLetterSheet">
+ .fl:first-letter { }
+</style>
+
+<style id="emptySheet">
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("firstLetterSheet").textContent = "";
+ document.getElementById("emptySheet").textContent = ".aft:after { content: counter(chicken); }";
+}
+
+</script>
+
+</head>
+
+<body onload="boom()">
+
+<div class="fl">Foo <span class="aft">Bar</span></div>
+
+<p class="aft">Baz</p>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/379920-1.svg b/layout/base/crashtests/379920-1.svg
new file mode 100644
index 000000000..6021e7e00
--- /dev/null
+++ b/layout/base/crashtests/379920-1.svg
@@ -0,0 +1,7 @@
+<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' onload="document.documentElement.style.MozBinding = 'url(#foo)';">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo"><content></content></binding>
+</bindings>
+
+</svg>
diff --git a/layout/base/crashtests/379920-2.svg b/layout/base/crashtests/379920-2.svg
new file mode 100644
index 000000000..71289c17b
--- /dev/null
+++ b/layout/base/crashtests/379920-2.svg
@@ -0,0 +1,7 @@
+<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' style="-moz-binding: url(#foo)">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo"><content></content></binding>
+</bindings>
+
+</svg>
diff --git a/layout/base/crashtests/379975.html b/layout/base/crashtests/379975.html
new file mode 100644
index 000000000..280fe7e96
--- /dev/null
+++ b/layout/base/crashtests/379975.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<!--
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="a">
+ <content></content>
+ </binding>
+</bindings>
+-->
+<html style="-moz-binding: url(data:text/xml,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%20%20%3Cbinding%20id%3D%22a%22%3E%0A%20%20%20%20%3Ccontent%3E%3C/content%3E%0A%20%20%3C/binding%3E%0A%3C/bindings%3E);">
+ <head>
+ <title>Crash</title>
+ <script type="text/javascript" src="data:text/plain,document.documentElement;"></script>
+ </head>
+</html>
diff --git a/layout/base/crashtests/380096-1.html b/layout/base/crashtests/380096-1.html
new file mode 100644
index 000000000..53100674c
--- /dev/null
+++ b/layout/base/crashtests/380096-1.html
@@ -0,0 +1,4 @@
+<html style="display: inline-table">
+<head style="display: table-caption"></head>
+<body onload="document.body.style.cssFloat = 'left';"></body>
+</html>
diff --git a/layout/base/crashtests/382204-1.html b/layout/base/crashtests/382204-1.html
new file mode 100644
index 000000000..0ecac4cc7
--- /dev/null
+++ b/layout/base/crashtests/382204-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+
+<html style="display: table;" class="reftest-wait">
+
+<head>
+<script>
+function boom()
+{
+ document.documentElement.style.color = "blue";
+ document.getElementById("zeta").style.display = "inline";
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+ <div id="zeta">foo</div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/383102-1.xhtml b/layout/base/crashtests/383102-1.xhtml
new file mode 100644
index 000000000..a7661ef7d
--- /dev/null
+++ b/layout/base/crashtests/383102-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+<body>
+
+<xul:hbox>
+ <xul:hbox>
+ <xul:listboxbody><xul:hbox/><span><div></div></span></xul:listboxbody>
+ </xul:hbox>
+ <xul:toolbarbutton/>
+</xul:hbox>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/383129-1-inner.xhtml b/layout/base/crashtests/383129-1-inner.xhtml
new file mode 100644
index 000000000..2b305298c
--- /dev/null
+++ b/layout/base/crashtests/383129-1-inner.xhtml
@@ -0,0 +1,22 @@
+<treerow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="zebra">
+<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function doe(){
+ document.getElementById('b').parentNode.removeChild(document.getElementById('b'));
+ document.getElementById('c').parentNode.removeChild(document.getElementById('c'));
+}
+
+setTimeout(doe, 200);
+]]></script>
+
+<box id="a"/>
+<mtr xmlns="http://www.w3.org/1998/Math/MathML">
+<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="zebra" id="c"/>
+</mtr>
+<box style="display: inline;" id="b"/>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a { counter-reset: chicken 11 egg; }
+#b { counter-increment: chicken -1 egg; }
+*[class=zebra] { counter-increment: chicken 5; }
+</style>
+</treerow> \ No newline at end of file
diff --git a/layout/base/crashtests/383129-1.html b/layout/base/crashtests/383129-1.html
new file mode 100644
index 000000000..ff1ec7a2a
--- /dev/null
+++ b/layout/base/crashtests/383129-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="383129-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/383806-1.xhtml b/layout/base/crashtests/383806-1.xhtml
new file mode 100644
index 000000000..91d72b1ff
--- /dev/null
+++ b/layout/base/crashtests/383806-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+
+function boom()
+{
+ var listbox = document.getElementById("listbox");
+ var td = document.getElementById("td");
+
+ var listitem = document.createElementNS(XUL_NS, "listitem");
+
+ listbox.appendChild(listitem);
+ listbox.appendChild(td);
+}
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table><tbody><tr><td id="td">X</td></tr></tbody></table>
+
+<xul:listbox id="listbox"/>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/384344-1-inner.html b/layout/base/crashtests/384344-1-inner.html
new file mode 100644
index 000000000..81355759b
--- /dev/null
+++ b/layout/base/crashtests/384344-1-inner.html
@@ -0,0 +1,20 @@
+<table ><td id="mytd"><small>
+</a>&nbsp-
+<a >A9
+<a id="mya1">AOL
+
+
+
+
+
+
+
+<a id="mya2">Yahoo
+
+<script>
+ mytd.style.display = "-moz-grid";
+ mya2.style.display = "list-item";
+ mya1.style.cssFloat = "right";
+ setTimeout('mya1.style.overflow = "scroll"',100);
+</script>
+ \ No newline at end of file
diff --git a/layout/base/crashtests/384344-1.html b/layout/base/crashtests/384344-1.html
new file mode 100644
index 000000000..ea509bb52
--- /dev/null
+++ b/layout/base/crashtests/384344-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="384344-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/384392-1.xhtml b/layout/base/crashtests/384392-1.xhtml
new file mode 100644
index 000000000..a5f04c0f1
--- /dev/null
+++ b/layout/base/crashtests/384392-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var table = document.getElementById("table");
+ document.removeChild(document.documentElement);
+ document.appendChild(table);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+
+
+<table border="1" id="table">
+ <tr>
+ <td><input type="text" value="Textbox" /></td>
+ </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/384392-2.svg b/layout/base/crashtests/384392-2.svg
new file mode 100644
index 000000000..332406749
--- /dev/null
+++ b/layout/base/crashtests/384392-2.svg
@@ -0,0 +1,3 @@
+<circle xmlns="http://www.w3.org/2000/svg">
+ <foreignObject/>
+</circle> \ No newline at end of file
diff --git a/layout/base/crashtests/384649-1.xhtml b/layout/base/crashtests/384649-1.xhtml
new file mode 100644
index 000000000..e2ba50cde
--- /dev/null
+++ b/layout/base/crashtests/384649-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+/* use attribute selector instead of the .class shorthand to work around bug 379178 */
+
+*[class="fixed"] { position: fixed; }
+
+math, mtable, mtr { position: inherit; }
+
+</style>
+</head>
+
+<body>
+
+<div class="fixed">
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable>
+ <mtr class="fixed">
+ <mtd><mi>x</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mi>y</mi></mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/385354.html b/layout/base/crashtests/385354.html
new file mode 100644
index 000000000..7c5a6a0c3
--- /dev/null
+++ b/layout/base/crashtests/385354.html
@@ -0,0 +1,18 @@
+<html><head>
+<style>
+object::before { content:"before text";}
+</style>
+<script>
+function doe(){
+document.getElementById('a').setAttribute('style', 'overflow: scroll; font-family: Hiragino Kaku Gothic Std;');
+}
+setTimeout(doe,500);
+</script>
+</head>
+<body>
+<div style="text-align: right;width: -moz-intrinsic;">
+<object style="white-space: -moz-pre-wrap; word-spacing: 10px;"><span id="a">
+</span></object>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/385866-1.xhtml b/layout/base/crashtests/385866-1.xhtml
new file mode 100644
index 000000000..7ef6620c7
--- /dev/null
+++ b/layout/base/crashtests/385866-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+div, col { counter-reset: chicken; }
+</style>
+
+<script>
+function boom()
+{
+ var col = document.getElementById("col");
+ col.parentNode.removeChild(col);
+}
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div><col id="col" span="2"></col></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/385880-1.xhtml b/layout/base/crashtests/385880-1.xhtml
new file mode 100644
index 000000000..7c78da7cc
--- /dev/null
+++ b/layout/base/crashtests/385880-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul= "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<body>
+<table><xul:menubar style="display: table;" /></table>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/386266-1.html b/layout/base/crashtests/386266-1.html
new file mode 100644
index 000000000..60284433a
--- /dev/null
+++ b/layout/base/crashtests/386266-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style>
+#outer {
+ -moz-column-count: 2;
+}
+#inner {
+ border: 1px solid green;
+}
+</style>
+
+<style id="s">
+#inner {
+ float: right;
+ height: 1em;
+}
+</style>
+
+</head>
+
+<body onload="document.getElementById('s').disabled = true;">
+
+<div id="outer"><div id="inner"></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/386476.html b/layout/base/crashtests/386476.html
new file mode 100644
index 000000000..744ee85e8
--- /dev/null
+++ b/layout/base/crashtests/386476.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>bug 386476</title>
+ </head>
+ <body onload="setTimeout(function(){document.querySelector('textarea').setAttribute('dir','rtl')},0)">
+<textarea rows="8" cols="50" dir="ltr">text inside a textarea gone wild
+second line
+[url=http://bugzilla.mozilla.org/]טקסט[/url], [url=http://bugzilla.mozilla.org/]url[טקסט], [url=http://bugzilla.mozilla.org/]טקסט[/url].</textarea>
+ </body>
+</html>
diff --git a/layout/base/crashtests/387195-1.html b/layout/base/crashtests/387195-1.html
new file mode 100644
index 000000000..199c3a055
--- /dev/null
+++ b/layout/base/crashtests/387195-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<div style="display: table-header-group; text-indent: -20em; border: 1px dotted black;">foo</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/387195-2.xhtml b/layout/base/crashtests/387195-2.xhtml
new file mode 100644
index 000000000..811f147cb
--- /dev/null
+++ b/layout/base/crashtests/387195-2.xhtml
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<title>Testcase for bug </title>
+<head>
+<script type="text/javascript">
+function boom() {
+ var colgroup = document.createElementNS("http://www.w3.org/1999/xhtml", 'colgroup');
+ document.getElementById('thead').insertBefore(colgroup, null);
+}
+</script>
+
+<style type="text/css">
+ thead {border:3px solid purple;}
+</style>
+</head>
+
+
+<body onload="boom()">
+
+<table><thead id="thead"></thead></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/388715-1.html b/layout/base/crashtests/388715-1.html
new file mode 100644
index 000000000..be09591f8
--- /dev/null
+++ b/layout/base/crashtests/388715-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style type="text/css">
+#div:first-letter { float: left; color: lightgreen; }
+</style>
+
+<script type="text/javascript">
+function boom()
+{
+ document.getElementById("div").className = "anything";
+}
+</script>
+</head>
+
+<body onload="boom()">
+
+<div id="div"><span style="color: magenta">Foo</span> bar</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/390976-1.html b/layout/base/crashtests/390976-1.html
new file mode 100644
index 000000000..4f0f578e2
--- /dev/null
+++ b/layout/base/crashtests/390976-1.html
@@ -0,0 +1,22 @@
+<html>
+
+<head>
+<script>
+function boom()
+{
+ var aaa = document.getElementById("aaa");
+ var bbb = document.getElementById("bbb");
+ aaa.parentNode.insertBefore(bbb, aaa);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div><span><span style="display: table-caption;"></span><span id="aaa"><div></div></span></span></div>
+
+<b id="bbb" style="display: table-caption;"></b>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/393326-1-binding.xml b/layout/base/crashtests/393326-1-binding.xml
new file mode 100644
index 000000000..ca76a3389
--- /dev/null
+++ b/layout/base/crashtests/393326-1-binding.xml
@@ -0,0 +1,4 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<content><children/></content>
+</binding></bindings> \ No newline at end of file
diff --git a/layout/base/crashtests/393326-1.html b/layout/base/crashtests/393326-1.html
new file mode 100644
index 000000000..6a79f0077
--- /dev/null
+++ b/layout/base/crashtests/393326-1.html
@@ -0,0 +1,15 @@
+<html><head>
+<style>
+div::first-letter {}
+</style>
+</head>
+<body>
+
+<div style="position: fixed; ">
+<q></q>
+</div>
+<span>
+<span style="display: -moz-box; -moz-binding:url(393326-1-binding.xml#a);"></span>
+</span>
+</body></html>
+
diff --git a/layout/base/crashtests/393326-2.html b/layout/base/crashtests/393326-2.html
new file mode 100644
index 000000000..820da45b5
--- /dev/null
+++ b/layout/base/crashtests/393326-2.html
@@ -0,0 +1,15 @@
+<html><head>
+<style>
+div::first-letter {}
+</style>
+</head>
+<body>
+
+<div style="position: fixed; ">
+<q></q>
+</div>
+<span>
+<span style="display: -moz-box; -moz-binding:url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Ccontent%3E%3Cchildren/%3E%3C/content%3E%0A%3C/binding%3E%3C/bindings%3E);"></span>
+</span>
+</body></html>
+
diff --git a/layout/base/crashtests/393661-1.html b/layout/base/crashtests/393661-1.html
new file mode 100644
index 000000000..d2e256d5f
--- /dev/null
+++ b/layout/base/crashtests/393661-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style>
+#z:first-letter { float: right; }
+</style>
+<script>
+function boom()
+{
+ var z = document.getElementById("z");
+ z.removeChild(z.firstChild);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="z">abc</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/393801-1-inner.html b/layout/base/crashtests/393801-1-inner.html
new file mode 100644
index 000000000..b21ab557d
--- /dev/null
+++ b/layout/base/crashtests/393801-1-inner.html
@@ -0,0 +1,781 @@
+<html>
+<body>
+<body style="position: absolute; background: yellow;">
+ <div style="position: absolute; background: lightgreen;">p</div>
+ <div style="display: none;">
+
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/393801-1.html b/layout/base/crashtests/393801-1.html
new file mode 100644
index 000000000..bed934eff
--- /dev/null
+++ b/layout/base/crashtests/393801-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<iframe scrolling="no" src="393801-1-inner.html" width="200" height="200"></iframe>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-1-iframe.html b/layout/base/crashtests/394014-1-iframe.html
new file mode 100644
index 000000000..9cbd2a516
--- /dev/null
+++ b/layout/base/crashtests/394014-1-iframe.html
@@ -0,0 +1,21 @@
+<html><head></head>
+<body>
+<span id="a" style="display: none;">
+<span id="b">
+<span style="-moz-binding: url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Cimplementation%3E%0A%3Cconstructor%3E%0A%20%20this.style.outline%3D%27%27%3B%0A%3C/constructor%3E%0A%3C/implementation%3E%0A%3C/binding%3E%0A%3C/bindings%3E);"></span>
+</span>
+</span>
+<script>
+for (var i=0;i<document.getElementsByTagName('*').length;i++){
+document.getElementsByTagName('*')[i];
+}
+function doe2() {
+//alert('t');
+document.getElementById('b').addEventListener('DOMSubtreeModified', function(e) {window.frameElement.parentNode.removeChild(window.frameElement) }, true);
+document.body.style.display = 'none';
+document.getElementById('a').style.display = '';
+}
+setTimeout(doe2, 20);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-1-inner.html b/layout/base/crashtests/394014-1-inner.html
new file mode 100644
index 000000000..9262bf12d
--- /dev/null
+++ b/layout/base/crashtests/394014-1-inner.html
@@ -0,0 +1,10 @@
+<html><head>
+<title>Testcase bug 394014 - Crash [@ NS_ProcessNextEvent_P] with DOMSubtreeModified removing windows, binding and other stuff</title>
+</head>
+<body>
+<iframe src="394014-1-iframe.html"></iframe>
+<script>
+setInterval(function() {window.location.reload()}, 1000);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/394014-1.html b/layout/base/crashtests/394014-1.html
new file mode 100644
index 000000000..5338fcd5e
--- /dev/null
+++ b/layout/base/crashtests/394014-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="394014-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/394014-2-binding.xml b/layout/base/crashtests/394014-2-binding.xml
new file mode 100644
index 000000000..d9f481dcb
--- /dev/null
+++ b/layout/base/crashtests/394014-2-binding.xml
@@ -0,0 +1,6 @@
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:xlink="http://www.w3.org/1999/xlink">
+<binding id="c" inheritstyle="false">
+<content><children/></content>
+<implementation><constructor>
+</constructor></implementation>
+</binding></bindings> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-2-constructor.xml b/layout/base/crashtests/394014-2-constructor.xml
new file mode 100644
index 000000000..a5b48fcb9
--- /dev/null
+++ b/layout/base/crashtests/394014-2-constructor.xml
@@ -0,0 +1,10 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<implementation>
+<constructor>
+ window.frameElement.parentNode.removeChild(window.frameElement);
+</constructor>
+</implementation>
+<content><children/></content>
+</binding>
+</bindings> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-2-constructordestructor.xml b/layout/base/crashtests/394014-2-constructordestructor.xml
new file mode 100644
index 000000000..0c571a661
--- /dev/null
+++ b/layout/base/crashtests/394014-2-constructordestructor.xml
@@ -0,0 +1,12 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<implementation>
+<destructor>
+ window.frameElement.parentNode.removeChild(window.frameElement);
+</destructor>
+</implementation>
+<content>
+<children/>
+</content>
+</binding>
+</bindings> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-2-crash.html b/layout/base/crashtests/394014-2-crash.html
new file mode 100644
index 000000000..c16fec0a8
--- /dev/null
+++ b/layout/base/crashtests/394014-2-crash.html
@@ -0,0 +1,13 @@
+<html><head>
+</head><body>
+
+<span style=" -moz-binding: url(394014-2-constructordestructor.xml#a);"></span>
+
+<span style="-moz-binding: url(394014-2-constructor.xml#a);">
+
+<style>style {-moz-binding:url(394014-2-binding.xml#c);</style>
+
+<textarea></textarea>
+</span>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/394014-2.html b/layout/base/crashtests/394014-2.html
new file mode 100644
index 000000000..52933bf1f
--- /dev/null
+++ b/layout/base/crashtests/394014-2.html
@@ -0,0 +1,7 @@
+<html><head>
+<title>Testcase bug 394014 - Crash [@ NS_ProcessNextEvent_P] with DOMSubtreeModified removing windows, binding and other stuff</title>
+</head>
+<body>tt
+<iframe src="394014-2-crash.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/394150-1.xhtml b/layout/base/crashtests/394150-1.xhtml
new file mode 100644
index 000000000..b2349c9f8
--- /dev/null
+++ b/layout/base/crashtests/394150-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var ms = document.createElementNS("http://www.w3.org/1998/Math/MathML", "ms");
+ var textNode = document.getElementById("emptyset").firstChild;
+ var mrow = document.getElementById("mrow");
+
+ ms.appendChild(textNode); // *move* the text node from one place to another!
+ mrow.appendChild(ms);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+<merror><emptyset id="emptyset">
+ <mrow id="mrow"></mrow></emptyset></merror>
+</math>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/397011-1.xhtml b/layout/base/crashtests/397011-1.xhtml
new file mode 100644
index 000000000..6837efe38
--- /dev/null
+++ b/layout/base/crashtests/397011-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<div style="text-indent: 11.2px;">
+ <div style="-moz-column-count: 2;">
+ <span style="float: left;"></span>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/398510-1.xhtml b/layout/base/crashtests/398510-1.xhtml
new file mode 100644
index 000000000..af48c8e5e
--- /dev/null
+++ b/layout/base/crashtests/398510-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+mtd:first-letter { }
+</style>
+<script>
+function boom()
+{
+ var b = document.body;
+ document.documentElement.removeChild(b);
+ document.documentElement.offsetHeight;
+ document.documentElement.appendChild(b);
+
+ var t = document.getElementById('t');
+ t.removeChild(t.firstChild);
+}
+</script>
+</head>
+<body onload="boom();">
+<mtd xmlns="http://www.w3.org/1998/Math/MathML" id="t">s</mtd>
+</body>
+</html>
diff --git a/layout/base/crashtests/398733-1.html b/layout/base/crashtests/398733-1.html
new file mode 100644
index 000000000..812ef0203
--- /dev/null
+++ b/layout/base/crashtests/398733-1.html
@@ -0,0 +1,20 @@
+<html><head>
+<script>
+function doe2(i) {
+var x=document.getElementsByTagName('*');
+document.body.setAttribute('style', 'display: inline; position: relative;');
+document.body.offsetHeight;
+document.getElementById('a').setAttribute('style', '');
+document.getElementById('b').setAttribute('style', 'position: absolute;');
+}
+setTimeout(doe2,100);
+</script>
+</head>
+
+<body>
+<span id="b"></span>&#1593;
+<span id="a" style="position: absolute;">&#1593;
+<span style="position: absolute;"></span>
+</span>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/398733-2.html b/layout/base/crashtests/398733-2.html
new file mode 100644
index 000000000..2f794eb76
--- /dev/null
+++ b/layout/base/crashtests/398733-2.html
@@ -0,0 +1,9 @@
+<html>
+<body style="display: inline; position: relative;">&#1593;
+<span id="a" style="position: absolute;">&#1593;<span style="position: absolute;"></span></span>
+<script>
+document.body.offsetHeight;
+document.getElementById('a').setAttribute('style', '');
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/399132-1.xhtml b/layout/base/crashtests/399132-1.xhtml
new file mode 100644
index 000000000..cf7f760e8
--- /dev/null
+++ b/layout/base/crashtests/399132-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style id="style">
+.penguin { overflow: hidden; }
+.penguin:first-line { }
+</style>
+<script>
+function boom()
+{
+ document.getElementById("style").textContent += "";
+ document.getElementById("td").className = "penguin";
+}
+</script>
+</head>
+<body onload="boom();"><td id="td">Text</td></body>
+</html>
diff --git a/layout/base/crashtests/399219-1.xhtml b/layout/base/crashtests/399219-1.xhtml
new file mode 100644
index 000000000..0d6dffaf3
--- /dev/null
+++ b/layout/base/crashtests/399219-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("div").style.display = "none";
+ document.documentElement.style.display = "-moz-inline-grid";
+}
+</script>
+</head>
+<body onload="boom();">
+
+<xul:treeitem style="display: -moz-inline-grid;"><xul:hbox><span><div id="div"></div></span></xul:hbox></xul:treeitem>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399365-1.html b/layout/base/crashtests/399365-1.html
new file mode 100644
index 000000000..ab5f2d021
--- /dev/null
+++ b/layout/base/crashtests/399365-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ document.body.insertBefore(document.createTextNode("y"), document.body.firstChild);
+}
+</script>
+</head>
+
+<body style="white-space: pre; direction: rtl;" onload="boom();">
+e
+0
+ </body>
+
+</html>
diff --git a/layout/base/crashtests/399676-1.xhtml b/layout/base/crashtests/399676-1.xhtml
new file mode 100644
index 000000000..82b547e5e
--- /dev/null
+++ b/layout/base/crashtests/399676-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+</head>
+<body>
+<math:mtd><span style="float: right;" /></math:mtd>
+</body>
+</html>
diff --git a/layout/base/crashtests/399687-1.html b/layout/base/crashtests/399687-1.html
new file mode 100644
index 000000000..f0693e5a5
--- /dev/null
+++ b/layout/base/crashtests/399687-1.html
@@ -0,0 +1,38 @@
+<html>
+<head>
+<style>
+
+#colset {
+ width: 300pt;
+ height: 2in;
+ -moz-column-count: 3;
+ -moz-column-gap: 0;
+}
+
+.ocontainer {
+ height: 0;
+}
+
+.overflow {
+ height: 5in;
+}
+
+</style>
+
+<script>
+function boom()
+{
+ var newDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var colset = document.getElementById("colset");
+ colset.insertBefore(newDiv, colset.childNodes[1]);
+}
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div id="colset"><div class="ocontainer"><div class="overflow"></div></div> </div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399940-1.xhtml b/layout/base/crashtests/399940-1.xhtml
new file mode 100644
index 000000000..a8bf90967
--- /dev/null
+++ b/layout/base/crashtests/399940-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var textNode = document.createTextNode("a");
+ document.getElementById("mathy").appendChild(textNode);
+ document.documentElement.offsetHeight;
+ textNode.data = "bc";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table><span></span><math:mrow id="mathy" /></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399946-1.xhtml b/layout/base/crashtests/399946-1.xhtml
new file mode 100644
index 000000000..1632130d0
--- /dev/null
+++ b/layout/base/crashtests/399946-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="empty">
+ <content></content>
+ </binding>
+</bindings>
+
+<script>
+function boom()
+{
+ document.getElementById("frame").style.MozBinding = "url('#empty')";
+}
+
+window.addEventListener("load", boom, false);
+</script>
+
+</head>
+
+<math:mtd><frameset><frame id="frame"></frame></frameset></math:mtd>
+
+</html>
diff --git a/layout/base/crashtests/399951-1.html b/layout/base/crashtests/399951-1.html
new file mode 100644
index 000000000..733774d1c
--- /dev/null
+++ b/layout/base/crashtests/399951-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+</head>
+
+<body style="direction: rtl;" onload="document.body.style.direction = 'ltr';">
+
+<div style="white-space: pre;">
+.i
+ h
+ f
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399994-1.html b/layout/base/crashtests/399994-1.html
new file mode 100644
index 000000000..1b545d515
--- /dev/null
+++ b/layout/base/crashtests/399994-1.html
@@ -0,0 +1,11 @@
+<html class="reftest-print">
+<head>
+</head>
+<body>
+
+<div style="display: table; position: fixed;">
+ <div style="display: table-row; page-break-after: always;"></div>
+ <div style="display: table-row;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/400185-1.xul b/layout/base/crashtests/400185-1.xul
new file mode 100644
index 000000000..1ca944240
--- /dev/null
+++ b/layout/base/crashtests/400185-1.xul
@@ -0,0 +1,21 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(doe, 30);" class="reftest-wait">
+<popupgroup id="a"/>
+<listcols>
+<nativescrollbar id="c">
+<treecols/>
+</nativescrollbar>
+</listcols>
+
+<script>
+function doe() {
+ document.documentElement.id = "true";
+ document.documentElement.removeChild(document.getElementById('a'));
+ var ne = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 'popupgroup');
+ document.documentElement.appendChild(ne);
+ document.getElementById('c').appendChild(document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 'treecols'));
+ document.documentElement.removeChild(ne);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</window>
diff --git a/layout/base/crashtests/400445-1.xhtml b/layout/base/crashtests/400445-1.xhtml
new file mode 100644
index 000000000..9cb71dbbd
--- /dev/null
+++ b/layout/base/crashtests/400445-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var mtd1 = document.getElementById("mtd1");
+ var mtd2 = document.getElementById("mtd2");
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+
+ mtd1.appendChild(newSpan);
+ mtd1.removeAttribute("columnspan");
+ mtd2.setAttribute("columnspan", 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<math:mtd id="mtd1" columnspan="5" /><math:mtd id="mtd2" />
+</body>
+</html>
diff --git a/layout/base/crashtests/400904-1.xhtml b/layout/base/crashtests/400904-1.xhtml
new file mode 100644
index 000000000..a00f42fd0
--- /dev/null
+++ b/layout/base/crashtests/400904-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+ var mtd = document.getElementById("mtd");
+ var n = document.createElementNS(MATHML_NS, 'mrow');
+ mtd.appendChild(n);
+ mtd.setAttribute('rowspan', 7);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<math:mtd id="mtd"></math:mtd><math:mtr><math:mrow></math:mrow></math:mtr>
+</body>
+</html>
diff --git a/layout/base/crashtests/401589-1.xul b/layout/base/crashtests/401589-1.xul
new file mode 100644
index 000000000..5d815c2cb
--- /dev/null
+++ b/layout/base/crashtests/401589-1.xul
@@ -0,0 +1,29 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:math="http://www.w3.org/1998/Math/MathML"
+ class="reftest-wait"
+ onload="boom();">
+
+<html:style type="text/css">
+[class="mp"] { display: -moz-popup; }
+</html:style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("mtd").setAttribute("class", "mp");
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("mtd").setAttribute("class", "");
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<math:mtd id="mtd" />
+
+</window>
diff --git a/layout/base/crashtests/401734-1.html b/layout/base/crashtests/401734-1.html
new file mode 100644
index 000000000..6f341e04c
--- /dev/null
+++ b/layout/base/crashtests/401734-1.html
@@ -0,0 +1,17 @@
+<html><head>
+<script>
+function doe(){
+document.getElementById('a').style.display = 'none';
+}
+</script>
+</head>
+<body onload="document.body.offsetHeight; setTimeout(doe,0)">
+<div style="-moz-column-count: 2;width: 400px;">
+<span id="a">
+<span style="float: left; -moz-column-width: 100px;">
+&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;
+</span>
+</span>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/401734-2.html b/layout/base/crashtests/401734-2.html
new file mode 100644
index 000000000..ae89165ab
--- /dev/null
+++ b/layout/base/crashtests/401734-2.html
@@ -0,0 +1,17 @@
+<html><head>
+<script>
+function doe(){
+document.getElementById('a').style.display = 'none';
+}
+</script>
+</head>
+<body onload="document.body.offsetHeight; setTimeout(doe,0)">
+<div style="-moz-column-count: 2;width: 400px;">
+<span id="a">
+<span style="float: left; -moz-column-width: 100px;">
+a-a-a-a-a-a-a-a-a-a-a-a-a-a
+</span>
+</span>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/403048.html b/layout/base/crashtests/403048.html
new file mode 100644
index 000000000..c41018222
--- /dev/null
+++ b/layout/base/crashtests/403048.html
@@ -0,0 +1,10 @@
+<html><head></head>
+<body>
+<basefont style="position: absolute;">
+<div id="a" tabindex="1"><span style="position: absolute;"></span>
+</div>
+<script>
+var y=document.getElementById('a');
+y.focus();
+</script>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/403175-1.html b/layout/base/crashtests/403175-1.html
new file mode 100644
index 000000000..08764ff70
--- /dev/null
+++ b/layout/base/crashtests/403175-1.html
@@ -0,0 +1,30 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+var i = 0;
+
+function boom()
+{
+ ++i;
+
+ while (document.body.firstChild)
+ document.body.removeChild(document.body.firstChild);
+
+ var table = document.createElement("table");
+ document.body.appendChild(table);
+ document.documentElement.style.color = (i % 2) ? "red" : "magenta";
+ table.setAttribute("align", "right");
+
+ setTimeout(boom, 15);
+}
+
+function cont()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom(); setTimeout(cont, 1000);"></body>
+</html>
diff --git a/layout/base/crashtests/403245-1.html b/layout/base/crashtests/403245-1.html
new file mode 100644
index 000000000..5c5f73149
--- /dev/null
+++ b/layout/base/crashtests/403245-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style>
+
+#outer { float: left; }
+#outer:first-letter { float: left; color: magenta; }
+
+</style>
+</head>
+
+<body onload="document.getElementById('inner').style.counterReset = 'chicken';">
+
+<div id="outer"><div id="inner"></div>xy</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403454.html b/layout/base/crashtests/403454.html
new file mode 100644
index 000000000..14648f6f4
--- /dev/null
+++ b/layout/base/crashtests/403454.html
@@ -0,0 +1,37 @@
+<html class="reftest-wait">
+<head>
+
+<style>
+
+.dddd:before {
+ content: "generated";
+}
+
+</style>
+
+<script>
+
+function b()
+{
+ document.getElementById("float").style.cssFloat = "";
+ setTimeout(b2, 30);
+}
+
+// This is just for visual effect, to make the timing clear.
+// It's not needed for the crash.
+function b2()
+{
+ document.body.style.background = "#eee";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="document.body.offsetHeight; setTimeout(b, 0);">
+
+<span class="dddd"><div></div><span id="float" style="float: left"></span></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-1.xhtml b/layout/base/crashtests/403569-1.xhtml
new file mode 100644
index 000000000..a79340d2a
--- /dev/null
+++ b/layout/base/crashtests/403569-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+#a {
+ -moz-column-count: 2;
+ -moz-column-width: 100px;
+ float: left;
+ border: 2px solid magenta;
+ height: 200px;
+}
+
+#b {
+ -moz-column-count: 2;
+ -moz-column-width: 100px;
+ float: left;
+ border: 2px solid green;
+ height: 300px;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('span').style.display = '-moz-inline-grid';">
+
+<div id="a"><div id="b"></div><optgroup label="foo"><span id="span"></span></optgroup></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-2.xhtml b/layout/base/crashtests/403569-2.xhtml
new file mode 100644
index 000000000..ed1cd07d8
--- /dev/null
+++ b/layout/base/crashtests/403569-2.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+body {
+ -moz-column-count: 2;
+ -moz-column-width: 100px;
+ height: 200px;
+}
+#b {
+ float: left;
+ height: 300px;
+}
+</style>
+</head>
+<body onclick="document.getElementById('span').style.display = 'block';">
+<img src="../../../testing/crashtest/images/tree.gif" width="1070" height="335" id="b"/>
+<optgroup label="foo"><span id="span"></span></optgroup>
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-3.xhtml b/layout/base/crashtests/403569-3.xhtml
new file mode 100644
index 000000000..67e283039
--- /dev/null
+++ b/layout/base/crashtests/403569-3.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+body {
+ -moz-column-count: 2;
+ -moz-column-width: 100px;
+ height: 200px;
+}
+#b {
+ float: left;
+ height: 300px;
+}
+
+.og:before {
+ display: block;
+ content: "foo";
+}
+
+</style>
+</head>
+<body onload="document.getElementById('span').style.display = 'block';">
+<img src="../../../testing/crashtest/images/tree.gif" width="1070" height="335" id="b"/>
+<div class="og"><span id="span"></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/404218-1.xhtml b/layout/base/crashtests/404218-1.xhtml
new file mode 100644
index 000000000..e1b7c683f
--- /dev/null
+++ b/layout/base/crashtests/404218-1.xhtml
@@ -0,0 +1,15 @@
+<treeitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<splitter/>
+<splitter>
+ <splitter id="a"/>
+</splitter>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+ document.getElementById('a').setAttribute('a', 'a');
+</script>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+treeitem, splitter {-moz-binding:url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%3Cbinding%20id%3D%22a%22%3E%3Ccontent%3E%3Cchildren/%3E%3C/content%3E%3C/binding%3E%3C/bindings%3E);}
+</style>
+
+</treeitem> \ No newline at end of file
diff --git a/layout/base/crashtests/404491-1.html b/layout/base/crashtests/404491-1.html
new file mode 100644
index 000000000..540a0f6a1
--- /dev/null
+++ b/layout/base/crashtests/404491-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<marquee><marquee></marquee><img></marquee>
+</body>
+</html>
diff --git a/layout/base/crashtests/404721-1.xhtml b/layout/base/crashtests/404721-1.xhtml
new file mode 100644
index 000000000..545a25772
--- /dev/null
+++ b/layout/base/crashtests/404721-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var s = document.getElementById("s");
+ s.parentNode.removeChild(s);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="-moz-column-width: 23px;"><div style="padding: 5px;"><span id="s"><div style="float: left;"><div style="width: 100px; height: 100px;"></div></div></span></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/404721-2.xhtml b/layout/base/crashtests/404721-2.xhtml
new file mode 100644
index 000000000..d65c2108b
--- /dev/null
+++ b/layout/base/crashtests/404721-2.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var s = document.getElementById("s");
+ s.parentNode.removeChild(s);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="-moz-column-width: 23px;"><div style="padding: 5px;"><div id="s"><td style="float: left;"><div style="width: 100px; height: 100px;"></div>
+ </td></div></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/405049-1.xul b/layout/base/crashtests/405049-1.xul
new file mode 100644
index 000000000..fb68de11e
--- /dev/null
+++ b/layout/base/crashtests/405049-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: table;">
+<box style="display: -moz-popup;"/>
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/405184-1.xhtml b/layout/base/crashtests/405184-1.xhtml
new file mode 100644
index 000000000..fcddd2832
--- /dev/null
+++ b/layout/base/crashtests/405184-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content>
+ <div xmlns="http://www.w3.org/1999/xhtml" style="position: fixed;">
+ <children xmlns="http://www.mozilla.org/xbl"/>
+ </div>
+ </content>
+ </binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var div = document.getElementById("div");
+ var caption = document.getElementById("caption");
+
+ div.removeChild(caption);
+ div.style.position = "inherit";
+}
+
+</script></head>
+
+<body onload="boom();">
+<div id="div" style="-moz-binding: url(#foo);"><caption id="caption"></caption></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/405186-1.xhtml b/layout/base/crashtests/405186-1.xhtml
new file mode 100644
index 000000000..ec08eab70
--- /dev/null
+++ b/layout/base/crashtests/405186-1.xhtml
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content>
+ <div xmlns="http://www.w3.org/1999/xhtml" style="position: fixed;">
+ <children xmlns="http://www.mozilla.org/xbl"/>
+ </div>
+ </content>
+ </binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var table = document.getElementById("table");
+ var tr = document.getElementById("tr");
+ var td = document.getElementById("td");
+
+ table.style.border = "2px dotted magenta";
+ tr.removeChild(td);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table id="table" style="-moz-binding: url(#foo);">
+ <tr id="tr">
+ <td id="td"></td>
+ </tr>
+</table>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/406675-1.html b/layout/base/crashtests/406675-1.html
new file mode 100644
index 000000000..779d82b67
--- /dev/null
+++ b/layout/base/crashtests/406675-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var textNode = document.createTextNode("\u202B" + "A B");
+ document.body.appendChild(textNode);
+ document.body.offsetHeight;
+ textNode.data = "\u202B" + " C";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/408292.html b/layout/base/crashtests/408292.html
new file mode 100644
index 000000000..0bc619b26
--- /dev/null
+++ b/layout/base/crashtests/408292.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style>
+#v {
+ -moz-column-count: 2;
+ width: 10ch;
+ height: 3.7em;
+ font: 14px monospace;
+ text-transform: lowercase;
+ direction: rtl;
+ border: 1px solid black;
+}
+</style>
+</head>
+<body onload="document.getElementById('v').style.direction = 'ltr';">
+<div id="v">aaaa bbbb cccc dddd eeee !</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/408299.html b/layout/base/crashtests/408299.html
new file mode 100644
index 000000000..a5b4a409e
--- /dev/null
+++ b/layout/base/crashtests/408299.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+
+<body style="direction: rtl; font-family: monospace;" onload="document.getElementById('v').style.width = '0';">
+
+<div id="v" style="-moz-column-count: 15; width: 1px; height: 2.7em; border: 1px solid black;">
+xxxxx yyyyy zzzzzz
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/408450-1.xhtml b/layout/base/crashtests/408450-1.xhtml
new file mode 100644
index 000000000..0744a6806
--- /dev/null
+++ b/layout/base/crashtests/408450-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<div style="-moz-column-count: 15;"><div style="-moz-column-count: 15;"><td style="display: block; height: 2.5em;"><div style="height: 0.5em;"></div></td></div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/409461-1.xhtml b/layout/base/crashtests/409461-1.xhtml
new file mode 100644
index 000000000..1abb42148
--- /dev/null
+++ b/layout/base/crashtests/409461-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+svg:after { content: 'generated'; }
+
+</style>
+</head>
+
+<body>
+
+<svg xmlns="http://www.w3.org/2000/svg" />
+
+</body>
+</html>
diff --git a/layout/base/crashtests/409513.html b/layout/base/crashtests/409513.html
new file mode 100644
index 000000000..a2aff4462
--- /dev/null
+++ b/layout/base/crashtests/409513.html
@@ -0,0 +1,14 @@
+<DOCTYPE html>
+<html>
+<head>
+</head>
+
+<body style="visibility: collapse;" onload="document.getElementById('div').style.direction = 'rtl';">
+
+<div style="display: -moz-groupbox;" id="div">
+aaaaa bbbbb ccccc ddddd eeeee fffff ggggg hhhhh iiiii
+</div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/410967.html b/layout/base/crashtests/410967.html
new file mode 100644
index 000000000..4895384f5
--- /dev/null
+++ b/layout/base/crashtests/410967.html
@@ -0,0 +1,17 @@
+<DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var t = document.body.firstChild;
+ t.data = "a" + t.data;
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="width: 1px;">b c&#1603;</body>
+
+</html>
diff --git a/layout/base/crashtests/411870-1.html b/layout/base/crashtests/411870-1.html
new file mode 100644
index 000000000..7b1495250
--- /dev/null
+++ b/layout/base/crashtests/411870-1.html
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<head>
+<script>
+
+function boom()
+{
+ document.body.appendChild(document.getElementById("v").firstChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="white-space: pre; -moz-column-count: -1;"><div id="v"><span>
+</span>
+
+</div></body>
+
+</html>
diff --git a/layout/base/crashtests/412651-1-frame.xhtml b/layout/base/crashtests/412651-1-frame.xhtml
new file mode 100644
index 000000000..80f10b544
--- /dev/null
+++ b/layout/base/crashtests/412651-1-frame.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<head>
+
+<style type="text/css" id="s"></style>
+
+<script type="text/javascript">
+
+function lo()
+{
+ window.onerror = oe;
+ setTimeout(function(){ location.reload(); }, 200);
+}
+
+function oe(a,b,c)
+{
+ document.getElementById("s").textContent = ".nosuch { color: red }";
+}
+
+</script>
+
+</head>
+
+<body onload="lo();">
+ <xul:preference/>
+ <xul:tabs/>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/412651-1.html b/layout/base/crashtests/412651-1.html
new file mode 100644
index 000000000..4640061ca
--- /dev/null
+++ b/layout/base/crashtests/412651-1.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var childLoads = 0;
+function inc()
+{
+ ++childLoads;
+ if (childLoads >= 2)
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<iframe src="412651-1-frame.xhtml" onload="inc();"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/413587-1.svg b/layout/base/crashtests/413587-1.svg
new file mode 100644
index 000000000..7781d5ef9
--- /dev/null
+++ b/layout/base/crashtests/413587-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <symbol id="foo">
+ <style type="text/css">
+ svg { counter-increment: x; }
+ </style>
+ </symbol>
+
+ <use xlink:href="#foo"/>
+
+</svg>
diff --git a/layout/base/crashtests/414058-1.html b/layout/base/crashtests/414058-1.html
new file mode 100644
index 000000000..f67a9d40d
--- /dev/null
+++ b/layout/base/crashtests/414058-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.style.MozBinding = "url('#none')";
+ document.body.offsetHeight;
+ document.removeChild(document.documentElement)
+ document.appendChild(document.createElement("div"));
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/414175-1.xul b/layout/base/crashtests/414175-1.xul
new file mode 100644
index 000000000..6d3c3d31f
--- /dev/null
+++ b/layout/base/crashtests/414175-1.xul
@@ -0,0 +1,26 @@
+<xul xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var oldListbox = document.getElementById("oldListbox");
+ var listitem = document.getElementById("listitem");
+ var newListbox = document.createElementNS(XUL_NS, "listbox");
+ var newHbox = document.createElementNS(XUL_NS, "hbox");
+
+ oldListbox.appendChild(newListbox);
+ listitem.appendChild(newHbox);
+
+ newListbox.style.display = "inline";
+ oldListbox.style.display = "block";
+ newHbox.style.display = "inline";
+}
+
+</script>
+
+<listbox id="oldListbox"><listitem id="listitem" /></listbox>
+
+</xul>
diff --git a/layout/base/crashtests/415503.xhtml b/layout/base/crashtests/415503.xhtml
new file mode 100644
index 000000000..b2fcae89f
--- /dev/null
+++ b/layout/base/crashtests/415503.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+
+<style id="ss" type="text/css">
+
+span, popupgroup { display: table; position: absolute; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var ss = document.getElementById("ss");
+ ss.removeChild(ss.firstChild);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<span><xul:popupgroup/></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/416107.xhtml b/layout/base/crashtests/416107.xhtml
new file mode 100644
index 000000000..753e48aca
--- /dev/null
+++ b/layout/base/crashtests/416107.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var z = document.getElementById("z");
+ var c = document.getElementById("c");
+
+ z.removeChild(z.firstChild);
+ document.body.offsetHeight;
+ c.style.counterReset = "c";
+}
+
+</script>
+
+</head>
+
+<body onload="boom();" style="font-family: monospace; width: 7ch;">
+
+<span style="margin: 8px;"></span>
+
+<span style="position: relative;" id="z">OOO<span style="display: table-caption;">OOOOOO</span><span id="c" style="position: absolute;"></span></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/419985.html b/layout/base/crashtests/419985.html
new file mode 100644
index 000000000..2f7360dfa
--- /dev/null
+++ b/layout/base/crashtests/419985.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<title>Crash [@ nsView::~nsView()] with onload focusing and removing window</title>
+</head>
+<body>
+<iframe id="content" onload="doe()" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%20onfocus%3D%22window.frameElement.parentNode.removeChild%28window.frameElement%29%22%3E%0A%3Ciframe%20src%3D%22data%3Atext/html%3Bcharset%3Dutf-8%2C%253Cbody%2520onload%253D%2522document.links%255B0%255D.focus%2528%2529%253B%2522%253E%253Ca%2520href%253D%2522javascript%253A%2522%253Em%253C/a%253E%22%3E%3C/iframe%3E%0A%3Cstyle%20id%3D%22e%22%3E%0A@import%20URL%28416107.xhtml%29%3B%0A%3C/style%3E%0A%3C/body%3E%0A%3C/html%3E"></iframe>
+
+<script>
+// Run the test for 2 seconds
+setTimeout(function() {
+ clearInterval(i);
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+
+function doe2() {
+document.getElementById('content').src = document.getElementById('content').src;
+}
+var i = setInterval(doe2, 400);
+
+function doe(){
+document.getElementById('content').style.display = 'none';
+document.body.offsetHeight;
+document.getElementById('content').style.display = '';
+}
+</script>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/420031-1.html b/layout/base/crashtests/420031-1.html
new file mode 100644
index 000000000..e91d19471
--- /dev/null
+++ b/layout/base/crashtests/420031-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="var s = document.getElementById('s'); s.parentNode.removeChild(s);">
+<div style="height: 4em; -moz-column-count: 1;"><br><span id="s">foo<div style="float: right;">bar<div></div> baz</div></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/420213-1.html b/layout/base/crashtests/420213-1.html
new file mode 100644
index 000000000..656ddd382
--- /dev/null
+++ b/layout/base/crashtests/420213-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.body.style.width = '5px';"><div style="-moz-column-width: 1px;">X<span style="height: 10px; float: right;"></span></div></body>
+</html>
diff --git a/layout/base/crashtests/420219-1.html b/layout/base/crashtests/420219-1.html
new file mode 100644
index 000000000..6db7fd66e
--- /dev/null
+++ b/layout/base/crashtests/420219-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("a").style.counterReset = "s";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<map name="m"><area id="a"></map>
+
+<img usemap="#m" src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B">
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/420651-1.xhtml b/layout/base/crashtests/420651-1.xhtml
new file mode 100644
index 000000000..8246d8e25
--- /dev/null
+++ b/layout/base/crashtests/420651-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="-moz-column-count: 1; width: 10em; white-space: pre;">
+ <div style="padding: 12em; display: inline; white-space: normal;">
+ <input style="float: right;"></input></div>
+ </body></html>
diff --git a/layout/base/crashtests/421203-1.xul b/layout/base/crashtests/421203-1.xul
new file mode 100644
index 000000000..f13999769
--- /dev/null
+++ b/layout/base/crashtests/421203-1.xul
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<hbox flex="1" style="background: url(&quot;data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B&quot;); display: inline; direction: rtl;"/>
+
+</window>
diff --git a/layout/base/crashtests/421432.html b/layout/base/crashtests/421432.html
new file mode 100644
index 000000000..37f8bff8f
--- /dev/null
+++ b/layout/base/crashtests/421432.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Crash [@ DocumentViewerImpl::LoadComplete] with focusing and removing iframe on reload</title>
+</head>
+<body>
+<iframe id="content" onload="window.frames[0].focus()" style="width:1000px;height: 300px;"></iframe>
+<script>
+function doe2() {
+document.getElementById('content').src = 'data:text/html;charset=utf-8,%3Cscript%3Ewindow.addEventListener%28%27focus%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3C/script%3E';
+}
+setInterval(doe2, 500);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/422276.html b/layout/base/crashtests/422276.html
new file mode 100644
index 000000000..6d2a89b74
--- /dev/null
+++ b/layout/base/crashtests/422276.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+</head>
+<body>
+<div style="overflow: scroll;">
+ <div>
+ <q>
+ <span style="display: -moz-box;"></span>
+ </q>
+ </div>
+ <q></q>
+</div>
+
+<style>
+body *+* {quotes: "quote" "quote" !important;}
+</style>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/423107-1.xhtml b/layout/base/crashtests/423107-1.xhtml
new file mode 100644
index 000000000..372775bf6
--- /dev/null
+++ b/layout/base/crashtests/423107-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script type="text/javascript" >
+
+function boom()
+{
+ var a = document.getElementById("a");
+ document.body.removeChild(a);
+ document.body.offsetHeight;
+ document.body.appendChild(a);
+}
+
+</script>
+</head>
+
+<body style="-moz-column-count: 3;" onload="boom();">1<div style="height: 1em;"></div><div id="a">2<select style="float: right;"></select></div></body>
+
+</html>
diff --git a/layout/base/crashtests/425981-1.html b/layout/base/crashtests/425981-1.html
new file mode 100644
index 000000000..3af3d12f6
--- /dev/null
+++ b/layout/base/crashtests/425981-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+div:first-letter { float: left; }
+</style>
+<script>
+function boom()
+{
+ var v = document.getElementById("v");
+ var t = v.firstChild;
+ v.appendChild(document.createTextNode(" "));
+ v.removeChild(t);
+}
+</script>
+</head>
+<body onload="boom();"><div id="v" style="-moz-column-count: 2; width: 1px;">a b</div></body>
+</html>
diff --git a/layout/base/crashtests/428113.xhtml b/layout/base/crashtests/428113.xhtml
new file mode 100644
index 000000000..a319676a3
--- /dev/null
+++ b/layout/base/crashtests/428113.xhtml
@@ -0,0 +1,2 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="float: right;"><listitem/><listitem/><listitem/><listitem/><listitem/><listitem label="foo"/><listitem style="position: absolute;"/></listbox></html>
diff --git a/layout/base/crashtests/428138-1.html b/layout/base/crashtests/428138-1.html
new file mode 100644
index 000000000..470fc6302
--- /dev/null
+++ b/layout/base/crashtests/428138-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ var fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ fo.style.padding = "10em";
+ svg.appendChild(fo);
+ document.body.appendChild(svg);
+ document.body.offsetHeight;
+
+ var opt = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ fo.appendChild(opt);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/base/crashtests/428448-1.html b/layout/base/crashtests/428448-1.html
new file mode 100644
index 000000000..e7c6bc354
--- /dev/null
+++ b/layout/base/crashtests/428448-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+
+<body style="-moz-column-width: 1px"><span>!</span>
+<span style="float: left"></span>
+x</body>
+
+</html>
diff --git a/layout/base/crashtests/429088-1.html b/layout/base/crashtests/429088-1.html
new file mode 100644
index 000000000..53952915b
--- /dev/null
+++ b/layout/base/crashtests/429088-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMSubtreeModified", function(){}, false);
+
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+ var ms = document.createElementNS(MATHML_NS, "ms");
+ document.body.appendChild(ms);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/429088-2.html b/layout/base/crashtests/429088-2.html
new file mode 100644
index 000000000..dc0caf8e5
--- /dev/null
+++ b/layout/base/crashtests/429088-2.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMSubtreeModified", function(){}, false);
+
+ var span = document.createElement("span");
+ document.body.appendChild(span);
+}
+
+</script>
+
+<style type="text/css">
+
+span:before { content: '0'; }
+span:after { content: '1'; }
+
+</style>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/429780-1.xhtml b/layout/base/crashtests/429780-1.xhtml
new file mode 100644
index 000000000..7754cb5d7
--- /dev/null
+++ b/layout/base/crashtests/429780-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<script>window.addEventListener("load", function() { document.getElementById("v").style.MozBinding = "url(#foo)"; }, false);</script>
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content></content></binding></bindings>
+</head>X<span><div id="v"></div></span></html>
diff --git a/layout/base/crashtests/429865-1.html b/layout/base/crashtests/429865-1.html
new file mode 100644
index 000000000..18b7bcbfb
--- /dev/null
+++ b/layout/base/crashtests/429865-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body:after { content: '0'; }
+body:first-letter { float: right; }
+
+</style>
+</head>
+
+<body> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/429881.html b/layout/base/crashtests/429881.html
new file mode 100644
index 000000000..afb4d6db9
--- /dev/null
+++ b/layout/base/crashtests/429881.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="position: relative; -moz-column-width: 5em;" onload="document.body.removeChild(document.body.firstChild)"><div id="d"><div style="white-space: pre; position: absolute;">
+B<div style="position: fixed;"></div></div></div></body>
+</html>
diff --git a/layout/base/crashtests/430569-1.html b/layout/base/crashtests/430569-1.html
new file mode 100644
index 000000000..32e84e3cb
--- /dev/null
+++ b/layout/base/crashtests/430569-1.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<html style="height: 12px; white-space: pre;"><body style="position: fixed; height: inherit; -moz-column-width: 1px;" onload="document.documentElement.style.height = '';"><div></div>
+</body></html>
diff --git a/layout/base/crashtests/430569-2.html b/layout/base/crashtests/430569-2.html
new file mode 100644
index 000000000..3d756db79
--- /dev/null
+++ b/layout/base/crashtests/430569-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- This height has to be less than 18px to trigger crash, on Linux. -->
+<html style="height: 10px;
+ background: lightblue"
+ ><body style="position: fixed;
+ height: inherit;
+ -moz-column-count: 1;
+ background: yellow;
+ width: 100px"
+ onload="document.documentElement.style.height = ''"
+ ><div style="outline: 1px dotted green"></div><br/></body></html>
diff --git a/layout/base/crashtests/432752-1.svg b/layout/base/crashtests/432752-1.svg
new file mode 100644
index 000000000..f5ea2aeb9
--- /dev/null
+++ b/layout/base/crashtests/432752-1.svg
@@ -0,0 +1,27 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ onload="boom();">
+
+ <script type="text/javascript">
+
+ function boom()
+ {
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ var d = document.getElementById("d");
+
+ d.appendChild(b);
+ a.appendChild(document.createTextNode("A"));
+ }
+
+ </script>
+
+ <g id="a"></g>
+
+ <use xlink:href="#a" id="b"/>
+
+ <use xlink:href="#d">
+ <g id="d"/>
+ </use>
+
+</svg>
diff --git a/layout/base/crashtests/433450-1.html b/layout/base/crashtests/433450-1.html
new file mode 100644
index 000000000..0d5b9f569
--- /dev/null
+++ b/layout/base/crashtests/433450-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
+
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("v").style.border = "1px solid blue";
+ document.getElementById("li").style.padding = "5px 7px";
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"><div style="-moz-column-count: 2;"><div style="margin-bottom: 5px;" id="v"></div><li id="li" style="border: 1px solid green;">
+<span style="border: 1px solid red; float: left;"></span></li></div></body>
+</html>
diff --git a/layout/base/crashtests/436982-1.html b/layout/base/crashtests/436982-1.html
new file mode 100644
index 000000000..425961b1a
--- /dev/null
+++ b/layout/base/crashtests/436982-1.html
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<style>
+
+div:first-letter { float: right; }
+
+</style>
+</head><body onload="document.getElementById('a').style.fontFamily = 'a';"><div id="a"> &#x202B;<span></span></div></body></html>
diff --git a/layout/base/crashtests/437142-1.html b/layout/base/crashtests/437142-1.html
new file mode 100644
index 000000000..6500e9fbe
--- /dev/null
+++ b/layout/base/crashtests/437142-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var m = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow");
+ document.body.appendChild(m);
+ document.getElementById("a").style.display = "inline";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<img usemap="#Map" src="data:image/gif,GIF87a%02%00%02%00%B3%00%00%00%00%00%FF%FF%FF%00%00%00%00%00%00%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%2C%00%00%00%00%02%00%02%00%00%04%03%90H%12%00%3B">
+
+<map name="Map"><area id="a"></map>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/439258-1.html b/layout/base/crashtests/439258-1.html
new file mode 100644
index 000000000..87b6f98d7
--- /dev/null
+++ b/layout/base/crashtests/439258-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("div").focus();
+ document.execCommand("bold", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="div" style="position: absolute" contenteditable="true"></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/439343.html b/layout/base/crashtests/439343.html
new file mode 100644
index 000000000..9537c9e5f
--- /dev/null
+++ b/layout/base/crashtests/439343.html
@@ -0,0 +1,2 @@
+<textarea style="text-shadow: black 0px 0px 5px;text-indent: -9999999999999999px;font-size: 900;letter-spacing: 10em;">
+mmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm \ No newline at end of file
diff --git a/layout/base/crashtests/444863-1.html b/layout/base/crashtests/444863-1.html
new file mode 100644
index 000000000..3a1bc206b
--- /dev/null
+++ b/layout/base/crashtests/444863-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+#a {
+ float: left;
+ position: relative;
+ padding: 331890106943cm 0;
+}
+
+#b {
+ position: absolute;
+ top: 100%;
+}
+
+
+</style>
+</head>
+
+<body>
+<div id="a"><div id="b"></div></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/444925-1.xul b/layout/base/crashtests/444925-1.xul
new file mode 100644
index 000000000..adf5603b9
--- /dev/null
+++ b/layout/base/crashtests/444925-1.xul
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<box>
+m
+<mathml:munderover style="padding-left: 20%; padding-right: 50%; text-shadow: 0px 0px 3.5px orange; text-decoration: overline;">
+m
+<box/>
+<box/>
+</mathml:munderover>
+</box>
+</window>
diff --git a/layout/base/crashtests/444967-1.html b/layout/base/crashtests/444967-1.html
new file mode 100644
index 000000000..a45ee5941
--- /dev/null
+++ b/layout/base/crashtests/444967-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body {
+ margin: 0pt; direction: rtl; word-spacing: 68710545em;
+ background: url("data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%0A(%00%00%00(%08%02%00%00%00%E13w%BE%00%00%02'iCCPICC%20Profile%00%00x%9C%95%92%BFk%13a%18%C7%3F%97PZ%D4%86R%A3%88%22%DE%20%D6B%D43%E9%901m~H%92%23%9EIJ%9A%D0%25%B9K%93hr9.%97%F8%03%85n%AEN%BAfQ2tT%EA%24%01%17%3B%14%2C%15%5B%FC%0B%BAW%BAH%8D%C3y%17%10J%F1%81%17%3E%CF%C3%F3%3E%3F%BE%EF%0B%DE%9Fe%C3hz%80%96n%99%D9%7BK%E2J%B1%24N%EE3%C15%CEp%81%F9%B2%DA1%16%15E%E6D%3B%FA%8E%00%B0%7B%ABl%18%CD%99%D5W%5B%2FS%07%5B%1F~%BF%D9%BD%3Aw9p%F2%3D%00%7C%E6J%B1%04B%00%F0%D7l%8E%00%FE%8A%CDy%C0%FF%D82%2C%10%EA%80_%AD%975%10%9E%03%013%9F%8D%820%00%7C5%9B%3F%02%BE%8A%CD_%00_O%ADY%20%EC%03%92%AE5t%F0L%01a%AD%DAQ%C1%13%01jZGm%81g%00%02%ADV%5B%03%EF%00%B8%A9%1A%A6%05%DE%AF%C0%F5%95bI%B4G%EE%CB%90%DE%01%BE%8Dcw%A6%A1%FF%16%FC%A9ql%EE%05%9C%EF%C3%A7%A9q%ECp%1F%01%10.Nu%D6BA%00%84%B3%9B0%F1z4%3A%2C%C0%E4%3C%1C%EF%8DF%BF6G%A3%E3w%E0%FD%01%C3%23%B5k%F6%FE%EA%25%08%DBp%9Ao%EFl%FB%B0c9t%12%DB%BA%00%20%C1%C6%3A%3C%18%82%04%BC%1F%C2%8D%3D%98%BD%02J%04%F2%11%3C%A1%90sl%0D%01%98%8E%C5eY%0C.H%E1BB9%E5%CD%FF%DBZ%CD%AE%D3g%168%A7W2%F7%81K%C0%81a)y%87%3B%BD%5C%DC%E1%B5F%22%E9%B0V%8E%A5%1D~V%8Ff%DC%1C3%91u%F8a9%A58%5C%D5%97sn%FD%A6%22%8F%7B-%B9%F9%D5N%3C7%AE%99%2F8lv%B3%CB%0E%3Fj%A7%DD%7C%AD%1Asg%D3%9B%19%B7f%C3J%BA%F3%13%23%8E%8C%8CH%90%05%24%C2%14H%A0%D8%7F%12%60f%03%FA%F3w%A5P%EE%F3va%FD_%9D%AC%EA%13%0B%20%DA6%9E%9A%8DZ%DD%12%17%0D%A3Y%0D%88I%5D%BD%1D%10%83%92%14%E6%0Fy4%B1%B9%90%3E%3B%0B%00%00%02%FBIDATx%9C%ED%D9%3Br%DB%40%14E%C1%A1%F7%BFg%3Aq%E0R%D9%FA%10%001g%A6%3BR%22%88%AC%BAO%01%CEc%AC%E8%F1x%BC%FC%C3%91%DF%9D%E1%F9%CB%7C%11%CF_%FB%F9%CB%7C%11%CF%F7%FC%C4%1F%F2%FCi%9F%0F%00%00%00%00%B0%8C_w%7F%80%ED%3C%9F%CF%BB%3F%02%7C%CDPa8%04%00%00%00%00%00%F86%E1%F9%23%99%81%04C%85%E1%10%880T%00%00%00%00%60%07%C23%5CBf%80%E1%10%880T%00%00%00%00%80%E3%84g6%253%C0p%08D%18*%00%00%00%00%C0%FC%84g%80y%E9m%24%18*%00%00%00%00%00%C23%93%921H0T%12%0C%15%00%00%00%00%80%AB%09%CF%BCH%C6%20%C1PI0T%00%00%00%00%00%EA%84%E7e%C9%18%24%18*%09%86%0A%00%00%00%00%00%9F%13%9Eo%23c%90%60%A8%24%18*%09%86%0A%00%00%00%00%2CLx%FE%2Fo%87I0T%12%0C%95%04C%05%00%00%00%00x%99%F0%0C%D7%921H0T%12%0C%15%00%00%00%00%60Z%C23%BB%931H0T%12%0C%15%00%00%00%00%60%5B%C23%B3%931H0T%12%0C%15%00%00%00%00%80%8B%08%CF%1C%25c%90%60%A8%24%18*%00%00%00%00%00Q%C2%F3%FAd%0C%12%0C%95%04C%05%00%00%00%00%80%7F%12%9E%EF'c%90%60%A8%24%18*%09%86%0A%00%00%00%00%ACGx%FE%9A%B7%C3%24%18*%09%86%0A%00%00%00%00%00K%12%9E%E1M%F46%12%0C%15%86C%00%00%00%00%00%F89%E1%19%FE%90%19H0T%18%0E%01%00%00%00%00%60%3E%C23%192%03%09%86%0A%C3!%00%00%00%00%00%ECGx%E642%03%40%85%FF%D8%00%00%00%00%00%9CKx%DE%88%CC%00%C3!%10a%A8%00%00%00%00%00%B4%08%CF%13%91%19%608%04%22%0C%15%00%00%00%00%00%FE%26%3C%FF%80%CC%00%C3!%10a%A8%00%00%00%00%00%F0N%C23%ACFo%23%C1PI0T%00%00%00%00%80o%12%9E%E1%DDd%0C%12%0C%95%04C%05%00%00%00%00%98%84%F0%0C%1F%C9%18%24%18*%09%86%0A%00%00%00%00%B0%09%E1%99%1E%19%83%04C%25%C1P%01%00%00%00%008%85%F0%CC%F9d%0C%12%0C%95%04C%05%00%00%00%00%20Ax%DE%91%8CA%82%A1%92%60%A8%00%00%00%00%000%84%E79%C9%18%24%18*%09%86%0A%00%00%00%00%00o%20%3C%BFB%C6%20%C1PI0T%12%0C%15%00%00%00%00%E0sk%86go%87I0T%12%0C%95%04C%05%00%00%00%00%B8%D7%9A%E1%19N!c%90%60%A8%24%18*%00%00%00%00%C0%DA%84g%C2d%0C%12%0C%95%04C%05%00%00%00%00%E0%08%E1%99%0B%C9%18%24%18*%09%86%0A%00%00%00%00%C0%CC%84%E7%AD%C9%18%24%18*%00%00%00%00%00%C0%E4%84%E7%A9%E9m%24%18*%0C%87%00%00%00%00%00%C0%DE%84%E7Cd%06%12%0C%15%86C%00%00%00%00%00%80%2B%FD%06%ECHi%F4%A12%1A%B2%00%00%00%00IEND%AEB%60%82");
+}
+</style>
+</head>
+<body>A B </body>
+</html>
diff --git a/layout/base/crashtests/446328-iframe.html b/layout/base/crashtests/446328-iframe.html
new file mode 100644
index 000000000..b67942b89
--- /dev/null
+++ b/layout/base/crashtests/446328-iframe.html
@@ -0,0 +1 @@
+<html><head></head><body style='border-image: url(446328.gif) 2 3 1 1 / 50px 50px'></body></html>
diff --git a/layout/base/crashtests/446328-top.html b/layout/base/crashtests/446328-top.html
new file mode 100644
index 000000000..9a184833a
--- /dev/null
+++ b/layout/base/crashtests/446328-top.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Bug 446328 – Crash [@ nsImageLoader::RedrawDirtyFrame] with document that has border-image and gets display: none</title>
+</head>
+<body>
+<iframe src="446328-iframe.html" id="a"></iframe>
+<script>
+function doe(){
+ var v = document.body.offsetHeight;
+ document.getElementById('a').style.display = 'none';
+ document.getElementById('a').style.display = 'inline';
+ v = document.body.offsetHeight;
+ document.getElementById('a').style.display = 'block';
+ document.getElementById('a').style.display = 'none';
+ v = document.body.offsetHeight;
+}
+setTimeout(doe,10);
+setTimeout("location.reload()",20);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/446328.gif b/layout/base/crashtests/446328.gif
new file mode 100644
index 000000000..9c5dd937f
--- /dev/null
+++ b/layout/base/crashtests/446328.gif
Binary files differ
diff --git a/layout/base/crashtests/446328.html b/layout/base/crashtests/446328.html
new file mode 100644
index 000000000..20061761b
--- /dev/null
+++ b/layout/base/crashtests/446328.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<head>
+<title>Bug 446328 – Crash [@ nsImageLoader::RedrawDirtyFrame] with document that has border-image and gets display: none</title>
+</head>
+<body>
+<iframe src="446328-top.html" id="a"></iframe>
+<script>
+function doe(){
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(doe,700)
+</script>
diff --git a/layout/base/crashtests/448488-1.html b/layout/base/crashtests/448488-1.html
new file mode 100644
index 000000000..d985cc32e
--- /dev/null
+++ b/layout/base/crashtests/448488-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body style="width: 17179869184ch"></body>
+</html>
diff --git a/layout/base/crashtests/448543-1.html b/layout/base/crashtests/448543-1.html
new file mode 100644
index 000000000..e44bcd4e2
--- /dev/null
+++ b/layout/base/crashtests/448543-1.html
@@ -0,0 +1,8 @@
+<html><head>
+</head>
+<body style="display: table;">
+
+<iframe></iframe>
+<video style="display: table-column-group;"></video>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/448543-2.html b/layout/base/crashtests/448543-2.html
new file mode 100644
index 000000000..9307b0c24
--- /dev/null
+++ b/layout/base/crashtests/448543-2.html
@@ -0,0 +1 @@
+<strike style="display: table-header-group;"><video style="display: table-row;"> \ No newline at end of file
diff --git a/layout/base/crashtests/448543-3.html b/layout/base/crashtests/448543-3.html
new file mode 100644
index 000000000..7ae8a170d
--- /dev/null
+++ b/layout/base/crashtests/448543-3.html
@@ -0,0 +1,7 @@
+<html><head>
+</head><body>
+<div style="display: table-row-group;">
+<iframe style="float: left;"></iframe>
+<video style="display: table;"></video>
+</div>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/450319-1.xhtml b/layout/base/crashtests/450319-1.xhtml
new file mode 100644
index 000000000..c073593c8
--- /dev/null
+++ b/layout/base/crashtests/450319-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var newSpan = document.createElement('span');
+ var mr = document.getElementById("mr");
+ mr.appendChild(newSpan);
+
+ var vv = document.getElementById("vv");
+ vv.parentNode.removeChild(vv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<style type="text/css">
+
+body { background: url("data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B"); }
+
+</style>
+</head>
+
+<div></div>
+
+<body onload="setTimeout(boom, 0);"><iframe /><m:mtd /><m:mrow id="mr" /></body>
+
+<span><div id="vv"></div></span>
+
+</html>
diff --git a/layout/base/crashtests/453894-1.xhtml b/layout/base/crashtests/453894-1.xhtml
new file mode 100644
index 000000000..7d8bd9557
--- /dev/null
+++ b/layout/base/crashtests/453894-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<head>
+<style type="text/css">
+
+.bg { background: url("data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B"); }
+
+</style>
+</head>
+
+<body>
+
+<table style="letter-spacing: 1300000px;"><tbody style="font-size: 9%;" class="bg"><tr><td>BBBBBBBBBBBBB BBBBBBBBBBBBB BBBBBBBBBBBBB BBBBBBBBBBBBB</td>X</tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/454751-1.xul b/layout/base/crashtests/454751-1.xul
new file mode 100644
index 000000000..83ec78599
--- /dev/null
+++ b/layout/base/crashtests/454751-1.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="addStyleSheet('window { display: table; }');">
+
+<script type="text/javascript">
+
+function addStyleSheet(text)
+{
+ var sheet = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ sheet.appendChild(document.createTextNode(text));
+ document.documentElement.appendChild(sheet);
+}
+
+</script>
+
+<treecols/>
+
+</window>
diff --git a/layout/base/crashtests/455063-1.html b/layout/base/crashtests/455063-1.html
new file mode 100644
index 000000000..bb8e5e9dd
--- /dev/null
+++ b/layout/base/crashtests/455063-1.html
@@ -0,0 +1,6 @@
+<html>
+<body onload="document.documentElement.style.display = 'table'">
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455063-2.html b/layout/base/crashtests/455063-2.html
new file mode 100644
index 000000000..666998eda
--- /dev/null
+++ b/layout/base/crashtests/455063-2.html
@@ -0,0 +1,6 @@
+<html style="display:table">
+<body onload="document.documentElement.style.display = 'block'">
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455063-3.html b/layout/base/crashtests/455063-3.html
new file mode 100644
index 000000000..20234d4ad
--- /dev/null
+++ b/layout/base/crashtests/455063-3.html
@@ -0,0 +1,6 @@
+<html style="display:block;-moz-column-count:2;column-count:2;">
+<body>
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455171-4.html b/layout/base/crashtests/455171-4.html
new file mode 100644
index 000000000..f85b91278
--- /dev/null
+++ b/layout/base/crashtests/455171-4.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Testcase, bug 455171</title>
+<div style="-moz-transform: translate(50px, 50px);"><div id="foo" style="position: fixed;"></div></div>
+<script type="text/javascript">
+var foo = document.getElementById("foo");
+var h = foo.offsetHeight;
+foo.parentNode.removeChild(foo);
+</script>
diff --git a/layout/base/crashtests/455623-1.html b/layout/base/crashtests/455623-1.html
new file mode 100644
index 000000000..3a363005b
--- /dev/null
+++ b/layout/base/crashtests/455623-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var i = document.getElementById("i");
+ i.contentDocument.designMode = "on";
+ i.previousSibling.data += "x\n";
+ i.style.counterReset = "c";
+}
+
+</script>
+</head>
+
+<body onload="boom();">&#x202E;<iframe id="i" src="data:text/html,a"></body>
+
+</html>
diff --git a/layout/base/crashtests/457362-1.xhtml b/layout/base/crashtests/457362-1.xhtml
new file mode 100644
index 000000000..4eeee1a8b
--- /dev/null
+++ b/layout/base/crashtests/457362-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="filter: url('#b');">
+
+<head></head>
+
+<body onload="document.getElementById('a').style.position = 'relative';">
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="float: left;" id="a"><treecols id="b"/></hbox>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/457514.html b/layout/base/crashtests/457514.html
new file mode 100644
index 000000000..6bf3e0b54
--- /dev/null
+++ b/layout/base/crashtests/457514.html
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var d = document.getElementById("d");
+ var s = document.getElementById("s");
+ s.insertBefore(document.createTextNode("T"), s.firstChild);
+ d.appendChild(s);
+ s.appendChild(document.createTextNode("\n"));
+}
+
+</script>
+
+<style type="text/css">
+
+div::first-letter { float: left; }
+
+</style>
+
+</head>
+
+<body onload="boom();"><div id="d"><span id="s">h</span></div></body>
+
+</html>
diff --git a/layout/base/crashtests/460389-1.html b/layout/base/crashtests/460389-1.html
new file mode 100644
index 000000000..cee1803b0
--- /dev/null
+++ b/layout/base/crashtests/460389-1.html
@@ -0,0 +1,6 @@
+<html>
+<head><style id="s">div:first-letter { float: left; }</style></head>
+<body onload="document.getElementById('s').disabled = true;">
+<div style="-moz-column-count: 2;"> &#x06CD;<div>T</div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/46043-1.html b/layout/base/crashtests/46043-1.html
new file mode 100644
index 000000000..cd394a018
--- /dev/null
+++ b/layout/base/crashtests/46043-1.html
@@ -0,0 +1,12 @@
+<html><head><title>Testcase for bug 46043</title></head>
+<body>
+
+<div style="float:right;width:600;background:blue">&nbsp;</div>
+<div style="float:right;width:400;background:yellow">&nbsp;</div>
+
+<ol>
+ <li>foo</li>
+</ol>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/462392.html b/layout/base/crashtests/462392.html
new file mode 100644
index 000000000..327d1c692
--- /dev/null
+++ b/layout/base/crashtests/462392.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+ <title>Crash</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <meta http-equiv="Content-Script-Type" content="text/javascript">
+ <style type="text/css">
+
+ </style>
+ <script type="text/javascript">
+
+ function run() {
+ var ifr = document.getElementById("if");
+ var cd = ifr.contentDocument;
+ cd.open();
+ cd.write("<body onresize='parent.setup(); location.reload()'>");
+ cd.close();
+
+ // resize the child
+ ifr.style.width = "500px";
+ }
+
+ function setup() {
+ var ifr = document.getElementById("if");
+ var cd = ifr.contentDocument;
+
+ // put a pending repaint on the child
+ cd.body.style.backgroundColor = 'green';
+
+ // put a pending reframe that destroys the frame on the parent
+ ifr.style.display = 'none';
+
+ // Let the location.reload() call RebuildAllStyleData.
+ }
+
+ </script>
+</head>
+<body onload="run()">
+
+<iframe id="if"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/466763-1.html b/layout/base/crashtests/466763-1.html
new file mode 100644
index 000000000..406720a32
--- /dev/null
+++ b/layout/base/crashtests/466763-1.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ s.appendChild(s.previousSibling);
+ s.parentNode.removeAttribute("class");
+ document.documentElement.offsetHeight;
+ s.appendChild(document.createTextNode(" "));
+}
+
+</script>
+<style type="text/css">
+
+.flfr:first-letter { float: right; }
+
+</style>
+</head>
+
+<body onload="boom();" class="flfr">&#xFEB7; <span id="s"></span></body>
+
+</html>
diff --git a/layout/base/crashtests/467881-1.html b/layout/base/crashtests/467881-1.html
new file mode 100644
index 000000000..f7f35877b
--- /dev/null
+++ b/layout/base/crashtests/467881-1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
+ var outer, inner, q;
+
+ function a()
+ {
+ outer = document.createElementNS(HTML_NS, "div");
+ inner = document.createElementNS(HTML_NS, "div");
+
+ inner.appendChild(document.createElementNS(HTML_NS, "iframe"));
+ inner.appendChild(document.createElementNS(HTML_NS, "div"));
+ inner.appendChild(q = document.createElementNS(HTML_NS, "span"));
+
+ outer.appendChild(inner);
+ document.documentElement.appendChild(outer);
+ setTimeout(b, 10);
+ }
+
+ function b()
+ {
+ outer.appendChild(document.createElementNS(HTML_NS, "span"));
+ setTimeout(c, 10);
+ }
+
+ function c()
+ {
+ q.appendChild(document.createElementNS(HTML_NS, "div"));
+ document.documentElement.removeAttribute("class");
+ }
+
+ a();
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+</head>
+
+<frameset></frameset>
+
+</html>
diff --git a/layout/base/crashtests/468491-1.html b/layout/base/crashtests/468491-1.html
new file mode 100644
index 000000000..bff857483
--- /dev/null
+++ b/layout/base/crashtests/468491-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+body::first-letter { float: left; }
+</style>
+</head>
+<body style="width:0">
+mm mm mm mm mm mm mm mm mm mm mm mm mm mm mm
+<span id="a"></span>
+<script>
+document.body.offsetWidth;
+document.getElementById('a').setAttribute('style', 'display: block;');
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/468546-1.xhtml b/layout/base/crashtests/468546-1.xhtml
new file mode 100644
index 000000000..af2340b1a
--- /dev/null
+++ b/layout/base/crashtests/468546-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+[class='anon'], #v { float: left; }
+[class='anon']:first-line { word-spacing: 30ch; }
+body { width: 30ch; }
+
+</style>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><div class="anon"><span>A BCDE</span><children xmlns="http://www.mozilla.org/xbl"/></div></content></binding></bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.insertBefore(document.createTextNode("fijkl"), document.body.firstChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="-moz-binding: url(#foo)"><div id="v">&#x202C;</div></body>
+
+</html>
diff --git a/layout/base/crashtests/468555-1.xhtml b/layout/base/crashtests/468555-1.xhtml
new file mode 100644
index 000000000..875bb098a
--- /dev/null
+++ b/layout/base/crashtests/468555-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+* { content: 'Y'; }
+table:after { content: inherit; }
+</style>
+</head>
+<body onload="document.getElementById('t').appendChild(document.createElement('span'));"><table id="t"></table></body>
+</html>
diff --git a/layout/base/crashtests/468563-1.html b/layout/base/crashtests/468563-1.html
new file mode 100644
index 000000000..5bf0b153f
--- /dev/null
+++ b/layout/base/crashtests/468563-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.getElementById('d').style.MozColumnWidth = '';">
+<div id="d" style="height: 1px; -moz-column-width: 1px;">d d <span style="position: absolute;"></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/468578-1.xhtml b/layout/base/crashtests/468578-1.xhtml
new file mode 100644
index 000000000..278ff51ae
--- /dev/null
+++ b/layout/base/crashtests/468578-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+// <![CDATA[
+
+function boom()
+{
+ var legend = document.getElementById("legend");
+ legend.appendChild(document.createTextNode("T"));
+ document.documentElement.offsetHeight;
+ legend.removeChild(legend.firstChild);
+ document.body.removeChild(legend);
+}
+
+// ]]>
+</script>
+</head>
+
+<body onload="boom();" style="-moz-column-width: 0pc;"><legend id="legend" style="white-space: pre-line; padding-bottom: 90px; display: block;">
+ </legend></body>
+</html>
diff --git a/layout/base/crashtests/468645-1.xhtml b/layout/base/crashtests/468645-1.xhtml
new file mode 100644
index 000000000..e12ed06a5
--- /dev/null
+++ b/layout/base/crashtests/468645-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-print">
+
+<div style="-moz-binding: url(#xbl); display: table-cell;">
+<span style="display: inline-block;">
+<input style="page-break-after: right;"/>
+</span>
+</div>
+
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="xbl" inheritstyle="false">
+ <resources>
+ <stylesheet src="data:text/css;charset=utf-8,"/>
+ </resources>
+</binding>
+</bindings>
+</html>
diff --git a/layout/base/crashtests/468645-2.xhtml b/layout/base/crashtests/468645-2.xhtml
new file mode 100644
index 000000000..d7a6bc59d
--- /dev/null
+++ b/layout/base/crashtests/468645-2.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-print">
+
+<div style="-moz-binding:url(#xbl)"/>
+<input style="page-break-after: left;"/>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="xbl" inheritstyle="false">
+ <resources>
+ <stylesheet src="data:text/css;charset=utf-8,"/>
+ </resources>
+</binding>
+</bindings>
+</html>
diff --git a/layout/base/crashtests/468645-3.xhtml b/layout/base/crashtests/468645-3.xhtml
new file mode 100644
index 000000000..10acc6632
--- /dev/null
+++ b/layout/base/crashtests/468645-3.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-print">
+<popup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+
+<style>html::before, window::before { content:""; display: table; position: fixed;}</style>
+</html>
diff --git a/layout/base/crashtests/469861-1.xhtml b/layout/base/crashtests/469861-1.xhtml
new file mode 100644
index 000000000..6a7888b62
--- /dev/null
+++ b/layout/base/crashtests/469861-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" >
+<head>
+<style type="text/css">
+
+math, mtable { position: fixed; }
+math { display: inline-table; }
+
+</style>
+</head>
+<body>
+
+<m:math><m:mtable></m:mtable></m:math>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/469861-2.xhtml b/layout/base/crashtests/469861-2.xhtml
new file mode 100644
index 000000000..295f2c61d
--- /dev/null
+++ b/layout/base/crashtests/469861-2.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" >
+<head>
+<style type="text/css">
+
+math, mtable { position: fixed; }
+math { display: table; }
+
+</style>
+</head>
+<body>
+
+<m:math><m:mtable></m:mtable></m:math>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/470851-1.xhtml b/layout/base/crashtests/470851-1.xhtml
new file mode 100644
index 000000000..5d23d760a
--- /dev/null
+++ b/layout/base/crashtests/470851-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-print">
+<thead>
+ <tbody style="top: 10%;position: relative;overflow: scroll;">
+ <td>
+ <tfoot>
+ <tr style="page-break-after: left;"></tr>
+ <tbody style="line-height: 999px;">m</tbody>
+ </tfoot>
+ <thead></thead>
+ </td>
+ </tbody>
+</thead>
+</html>
diff --git a/layout/base/crashtests/471594-1.xhtml b/layout/base/crashtests/471594-1.xhtml
new file mode 100644
index 000000000..16ccdb056
--- /dev/null
+++ b/layout/base/crashtests/471594-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="x"><content><SPAN>2!<children xmlns="http://www.mozilla.org/xbl"/></SPAN></content></binding>
+ <binding id="y"><content></content></binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("a").style.MozBinding = 'url("#y")';
+}
+
+</script>
+</head>
+
+<body dir="rtl" onload="boom();"><div style="-moz-binding: url(#x);"><span style="unicode-bidi: bidi-override;" id="a"></span></div></body>
+</html>
diff --git a/layout/base/crashtests/473042.xhtml b/layout/base/crashtests/473042.xhtml
new file mode 100644
index 000000000..a92b2481f
--- /dev/null
+++ b/layout/base/crashtests/473042.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: none;"><mrow xmlns="http://www.w3.org/1998/Math/MathML"></html> \ No newline at end of file
diff --git a/layout/base/crashtests/474075.html b/layout/base/crashtests/474075.html
new file mode 100644
index 000000000..8771bfa72
--- /dev/null
+++ b/layout/base/crashtests/474075.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+
+<body onload="document.getElementById('a').style.fontWeight = 'bold';document.documentElement.offsetHeight;">
+
+<div style="top: -2px; bottom: -8px; position: fixed; -moz-column-count: 1;"><div id="a" style="float: right; padding: 800px;"></div><div><div style="clear: right;"><div style="font-size-adjust: 1073741823; white-space: pre;">
+<input style="position: fixed;"></div></div></div></div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/477333-1.xhtml b/layout/base/crashtests/477333-1.xhtml
new file mode 100644
index 000000000..037dfa188
--- /dev/null
+++ b/layout/base/crashtests/477333-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="overflow-x: scroll">
+<head>
+<style type="text/css">
+
+body:first-letter { }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ td = document.createElement("td");
+ td.contentEditable = "true";
+ document.body.appendChild(td);
+ document.execCommand("strikethrough", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/477731-1.html b/layout/base/crashtests/477731-1.html
new file mode 100644
index 000000000..f017fa7cf
--- /dev/null
+++ b/layout/base/crashtests/477731-1.html
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">body:first-letter { float: left; }</style>
+</head>
+<body style="-moz-column-width: 100000px;" onload="document.body.style.MozColumnWidth='';"> &#x08D9;</body>
+</html>
diff --git a/layout/base/crashtests/47843-1.html b/layout/base/crashtests/47843-1.html
new file mode 100644
index 000000000..f8ce8b08d
--- /dev/null
+++ b/layout/base/crashtests/47843-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>Testcase for bug 47843</title>
+ <style type="text/css">
+ BODY {overflow:scroll;}
+ </style>
+ </head>
+ <body>
+ <P>Blah
+ </body>
+</html>
+
diff --git a/layout/base/crashtests/479114-1.html b/layout/base/crashtests/479114-1.html
new file mode 100644
index 000000000..8ed600d76
--- /dev/null
+++ b/layout/base/crashtests/479114-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+ <body>
+ <div style="display: table-row">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ <div style="display: table-row-group">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ <div style="display: table">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/479360-1.xhtml b/layout/base/crashtests/479360-1.xhtml
new file mode 100644
index 000000000..221d1c4a6
--- /dev/null
+++ b/layout/base/crashtests/479360-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.style.display = "none";
+ document.execCommand("removeformat", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><td contenteditable="true"></td></body>
+
+</html>
diff --git a/layout/base/crashtests/480686-1.html b/layout/base/crashtests/480686-1.html
new file mode 100644
index 000000000..8a4ba72d6
--- /dev/null
+++ b/layout/base/crashtests/480686-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+span { background: url(../../../testing/crashtest/images/tree.gif); }
+
+</style>
+</head>
+
+<body><div style="direction: rtl;"><div style="-moz-column-width: 1px;"><span>Q<input></span></div></div></body>
+
+</html>
diff --git a/layout/base/crashtests/481806-1.html b/layout/base/crashtests/481806-1.html
new file mode 100644
index 000000000..9b12557ee
--- /dev/null
+++ b/layout/base/crashtests/481806-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ var hbox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox");
+ document.removeChild(document.documentElement);
+ document.appendChild(hbox);
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/483604-1.xhtml b/layout/base/crashtests/483604-1.xhtml
new file mode 100644
index 000000000..2856951ca
--- /dev/null
+++ b/layout/base/crashtests/483604-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<style>colgroup::before { content:"b";}</style>
+<colgroup/>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/485501-1.html b/layout/base/crashtests/485501-1.html
new file mode 100644
index 000000000..67cbcb9b4
--- /dev/null
+++ b/layout/base/crashtests/485501-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<div style="overflow: hidden; border-radius: 50px; height: 200px; width: 200px; background:yellow">
+ This is some text that should get clipped at the corners by the border radius.
+</div>
diff --git a/layout/base/crashtests/487544-1.html b/layout/base/crashtests/487544-1.html
new file mode 100644
index 000000000..f548c4f66
--- /dev/null
+++ b/layout/base/crashtests/487544-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><body style="display: -moz-grid-group;"><br style="position: fixed;"></body></html>
diff --git a/layout/base/crashtests/488390-1.xhtml b/layout/base/crashtests/488390-1.xhtml
new file mode 100644
index 000000000..ef7568b59
--- /dev/null
+++ b/layout/base/crashtests/488390-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("select").removeAttribute("multiple");
+ // Trigger a reflow
+ document.body.style.width = 0;
+ // And flush layout
+ document.body.offsetWidth;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span>&#x202E;text text text text text&#x202C;<span><dir/></span><span><tr><select id="select" multiple="multiple"/></tr></span></span></body>
+</html>
diff --git a/layout/base/crashtests/489691.html b/layout/base/crashtests/489691.html
new file mode 100644
index 000000000..b9c165636
--- /dev/null
+++ b/layout/base/crashtests/489691.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+span:first-letter { }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("q").style.direction = "";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="q" style="direction: rtl; display: block;">B C
+</span></body></html>
diff --git a/layout/base/crashtests/490376-1.xhtml b/layout/base/crashtests/490376-1.xhtml
new file mode 100644
index 000000000..4ee606f92
--- /dev/null
+++ b/layout/base/crashtests/490376-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.body.innerHTML = "<table><caption><\/caption><iframe><\/iframe><\/table>";
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/490559-1.html b/layout/base/crashtests/490559-1.html
new file mode 100644
index 000000000..c3e09c1be
--- /dev/null
+++ b/layout/base/crashtests/490559-1.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script type="text/javascript">
+function ddoe() {
+ var x=document.getElementById('a');
+ x.parentNode.removeChild(x);
+}
+ </script>
+ </head>
+ <body onload="ddoe()">
+ <div style="width: 1px;">
+ <span id="a">&#68997;</span>
+&#68997;
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/490747.html b/layout/base/crashtests/490747.html
new file mode 100644
index 000000000..837ad9023
--- /dev/null
+++ b/layout/base/crashtests/490747.html
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+div:first-letter { }
+</style>
+</head>
+<body onload="document.getElementById('s').style.height = '700px';" style="text-align: justify;"><div><span id="s" style="unicode-bidi: bidi-override;">Hello &#x064A;&#x0646;</span></div></body>
+</html>
diff --git a/layout/base/crashtests/49122-1.html b/layout/base/crashtests/49122-1.html
new file mode 100644
index 000000000..7f2cc012f
--- /dev/null
+++ b/layout/base/crashtests/49122-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+<HEAD>
+<TITLE>Mozilla Bug 49122</TITLE>
+</HEAD>
+<BODY><FORM action="">
+<TABLE>
+<TR>
+<TD>
+<MAP NAME="blah">
+<AREA SHAPE="rect" COORDS="0,1,1,0" href="" alt="blah">
+</MAP>
+<IMG src="" USEMAP="#blah" alt="blah">
+</TD>
+</TR>
+</TABLE>
+<P>
+</FORM></BODY>
+</HTML>
diff --git a/layout/base/crashtests/491547-1.xul b/layout/base/crashtests/491547-1.xul
new file mode 100644
index 000000000..c2c0a28bc
--- /dev/null
+++ b/layout/base/crashtests/491547-1.xul
@@ -0,0 +1,20 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<box id="a" style="display: list-item;">
+&amp;m&amp;m
+<box id="b"/>
+</box>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a::first-letter {float: right; }
+</style>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe() {
+document.getElementById('a').style.direction = 'rtl';
+document.getElementById('b').style.direction = 'ltr';
+}
+setTimeout(doe, 100);
+</script>
+
+</window>
diff --git a/layout/base/crashtests/491547-2.xul b/layout/base/crashtests/491547-2.xul
new file mode 100644
index 000000000..6191a0734
--- /dev/null
+++ b/layout/base/crashtests/491547-2.xul
@@ -0,0 +1,31 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<box id="a" style="display: list-item;">
+&amp;c&amp;m&amp;q
+<box id="b"/>
+</box>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a::first-letter {float: right; color: red;}
+</style>
+
+<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function run() {
+ for (var i = 0; i < 20; i++) {
+ document.documentElement.offsetWidth;
+ doe();
+ document.documentElement.offsetWidth;
+ undoe();
+ }
+}
+function doe() {
+ document.getElementById('a').style.direction = 'rtl';
+ document.getElementById('b').style.direction = 'ltr';
+}
+function undoe() {
+ document.getElementById('a').style.direction = 'ltr';
+ document.getElementById('b').style.direction = 'rtl';
+}
+setTimeout(run, 100);
+]]></script>
+</window>
diff --git a/layout/base/crashtests/492014.xhtml b/layout/base/crashtests/492014.xhtml
new file mode 100644
index 000000000..b16622d69
--- /dev/null
+++ b/layout/base/crashtests/492014.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body onload="document.getElementsByTagName('td')[0].appendChild(document.createElement('iframe'));">
+<caption></caption><td></td>
+</body>
+</html>
diff --git a/layout/base/crashtests/492112-1.xhtml b/layout/base/crashtests/492112-1.xhtml
new file mode 100644
index 000000000..b3682a07b
--- /dev/null
+++ b/layout/base/crashtests/492112-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css" id="s">
+
+div {
+ color: green;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('s').disabled = true;"><colgroup> </colgroup></body>
+
+</html>
diff --git a/layout/base/crashtests/492163-1.xhtml b/layout/base/crashtests/492163-1.xhtml
new file mode 100644
index 000000000..51cb1d3cb
--- /dev/null
+++ b/layout/base/crashtests/492163-1.xhtml
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function addSheet(text)
+{
+ var head = document.getElementsByTagName("head")[0];
+ var sheet = document.createElement("style");
+ sheet.appendChild(document.createTextNode(text));
+ head.appendChild(sheet);
+}
+
+</script>
+
+<style>colgroup:before { content: '0'; }</style>
+
+</head>
+
+<body onload="addSheet('x { }');"><table><colgroup></colgroup></table></body>
+</html>
diff --git a/layout/base/crashtests/495350-1.html b/layout/base/crashtests/495350-1.html
new file mode 100644
index 000000000..ccab5b373
--- /dev/null
+++ b/layout/base/crashtests/495350-1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+<div style="display: -moz-inline-box;">
+<br style="position: fixed;">
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/496011-1.xhtml b/layout/base/crashtests/496011-1.xhtml
new file mode 100644
index 000000000..1fabb4dbe
--- /dev/null
+++ b/layout/base/crashtests/496011-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.body.setAttribute("contenteditable", "true");
+ document.execCommand("selectAll", false, null);
+ document.execCommand("inserthtml", false, "<span><div><\/div><\/span>");
+ document.execCommand("undo", false, null);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();"><textarea><span/></textarea></body>
+
+</html>
diff --git a/layout/base/crashtests/497519-1.xhtml b/layout/base/crashtests/497519-1.xhtml
new file mode 100644
index 000000000..ee4130053
--- /dev/null
+++ b/layout/base/crashtests/497519-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+<xbl:binding id="foo"><xbl:content><div style="position:relative;"><xbl:children/></div></xbl:content></xbl:binding>
+</xbl:bindings>
+
+<script>
+<![CDATA[
+function test() {
+ document.getElementById("span").innerHTML = "<table><tr><td></td></tr></table>";
+}
+]]>
+</script>
+
+</head>
+
+<body onload="test();">
+
+<div style="-moz-binding: url(#foo)">
+
+ <div style="display:none">text</div>
+ <span id="span">text</span>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/497519-2.xhtml b/layout/base/crashtests/497519-2.xhtml
new file mode 100644
index 000000000..5a08a6688
--- /dev/null
+++ b/layout/base/crashtests/497519-2.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(#foo)">
+<head>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+<xbl:binding id="foo"><xbl:content><fieldset><xbl:children/></fieldset></xbl:content></xbl:binding>
+</xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var dE = document.documentElement;
+ var leg = document.createElementNS("http://www.w3.org/1999/xhtml", "legend");
+ leg.style.cssFloat = "left";
+ dE.appendChild(leg);
+ document.documentElement.offsetHeight;
+ dE.removeChild(leg);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/base/crashtests/497519-3.xhtml b/layout/base/crashtests/497519-3.xhtml
new file mode 100644
index 000000000..035563f45
--- /dev/null
+++ b/layout/base/crashtests/497519-3.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(#foo)">
+<head>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+<xbl:binding id="foo"><xbl:content><fieldset><xbl:children/></fieldset></xbl:content></xbl:binding>
+</xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var dE = document.documentElement;
+ var leg = document.createElementNS("http://www.w3.org/1999/xhtml", "legend");
+ leg.appendChild(document.createTextNode("legend"));
+ dE.appendChild(leg);
+ document.documentElement.offsetHeight;
+ dE.removeChild(leg);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/base/crashtests/497519-4.xhtml b/layout/base/crashtests/497519-4.xhtml
new file mode 100644
index 000000000..2dfbaf77e
--- /dev/null
+++ b/layout/base/crashtests/497519-4.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(#foo)">
+<head>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+<xbl:binding id="foo"><xbl:content><fieldset><xbl:children/></fieldset></xbl:content></xbl:binding>
+</xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var dE = document.documentElement;
+ var leg = document.createElementNS("http://www.w3.org/1999/xhtml", "legend");
+ leg.style.position = "absolute";
+ dE.appendChild(leg);
+ document.documentElement.offsetHeight;
+ dE.removeChild(leg);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/base/crashtests/499741-1.xhtml b/layout/base/crashtests/499741-1.xhtml
new file mode 100644
index 000000000..ecabaf9f3
--- /dev/null
+++ b/layout/base/crashtests/499741-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-column-width: 1px;">a<div><span><wbr/>a<select/></span></div></html>
diff --git a/layout/base/crashtests/499841-1.xhtml b/layout/base/crashtests/499841-1.xhtml
new file mode 100644
index 000000000..28d0ec912
--- /dev/null
+++ b/layout/base/crashtests/499841-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('v').appendChild(document.getElementById('s'));">
+<style id="s">div:first-letter { float: right; } </style><div id="v"><span>AB</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/499858-1.xhtml b/layout/base/crashtests/499858-1.xhtml
new file mode 100644
index 000000000..a03fe3434
--- /dev/null
+++ b/layout/base/crashtests/499858-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="word-wrap: break-word; padding: 0pt 3870px; position: relative; -moz-column-count: 3;">
+<body onload="document.documentElement.style.visibility='hidden';">
+<div>,,, <span style="position: absolute;"><div/>2</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/500467-1.html b/layout/base/crashtests/500467-1.html
new file mode 100644
index 000000000..a2bf5a7c4
--- /dev/null
+++ b/layout/base/crashtests/500467-1.html
@@ -0,0 +1,23141 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script>
+ function doIt() {
+ var p = document.getElementById("p").tBodies[0];
+ var nodes = Array.prototype.slice.call(document.getElementsByTagName("tr"));
+ for (var i = 0; i < nodes.length; ++i) {
+ var n = nodes[i].nextSibling;
+ p.removeChild(nodes[i]);
+ p.insertBefore(nodes[i], n);
+ }
+ setTimeout('document.documentElement.className = ""', 0);
+ }
+ </script>
+ </head>
+ <body onload="doIt()">
+ <table id="p">
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/layout/base/crashtests/501878-1.html b/layout/base/crashtests/501878-1.html
new file mode 100644
index 000000000..879f163fb
--- /dev/null
+++ b/layout/base/crashtests/501878-1.html
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script type='text/javascript'>
+window.addEventListener("load", function(){ document.getElementById("x").appendChild(document.createTextNode(" ")); }, false);
+</script>
+<text id="x"><text style="position: absolute;"/>Hello</text></svg>
diff --git a/layout/base/crashtests/50257-1.html b/layout/base/crashtests/50257-1.html
new file mode 100644
index 000000000..a7dfd7b9a
--- /dev/null
+++ b/layout/base/crashtests/50257-1.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+<div style="margin-bottom: -1">
+<img height=1>
+</div>
+<table align=left>
+ <td>
+ <table>
+ <td>
+ </table>
+ </td>
+ <td>
+ <table cols=2>
+ <td>
+ </table>
+ </td>
+</table>
+<br clear="left">
+</body>
+</html>
diff --git a/layout/base/crashtests/503936-1.html b/layout/base/crashtests/503936-1.html
new file mode 100644
index 000000000..c1612e8a9
--- /dev/null
+++ b/layout/base/crashtests/503936-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var st = document.getElementById("s").firstChild
+
+ var range = document.createRange();
+ range.setStart(document.documentElement, 0);
+ range.setEnd(st, 1);
+ range.deleteContents()
+
+ try { range.surroundContents(st); } catch(e) { }
+}
+
+</script>
+
+<style type="text/css">
+
+div:first-letter { float: left; }
+
+</style>
+</head>
+
+<body onload="boom();"><div><span id="s">Foo</span></div></body>
+
+</html>
diff --git a/layout/base/crashtests/50395-1.html b/layout/base/crashtests/50395-1.html
new file mode 100644
index 000000000..42fc8d786
--- /dev/null
+++ b/layout/base/crashtests/50395-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <title>Testcase for bug 50395</title>
+ <style> * { overflow: auto; } </style>
+</head>
+<body>
+<h3>In head: &lt;style&gt; * { overflow: auto; } &lt;/style&gt;</h3>
+
+<p>iframe width="40%":</p>
+<iframe width="40%"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+<p>iframe height="10%"</p>
+<iframe height="10%"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+<p>iframe height="90"</p>
+<iframe height="90"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/507119.html b/layout/base/crashtests/507119.html
new file mode 100644
index 000000000..83cb3b20a
--- /dev/null
+++ b/layout/base/crashtests/507119.html
@@ -0,0 +1,554 @@
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
diff --git a/layout/base/crashtests/514104-1.xul b/layout/base/crashtests/514104-1.xul
new file mode 100644
index 000000000..bb410b5ce
--- /dev/null
+++ b/layout/base/crashtests/514104-1.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<!-- there must be no extra elements in the document -->
+
+<window onload="
+ document.documentElement.removeChild(document.getElementById('b'));
+ document.getElementById('l').removeChild(document.getElementById('h'));
+ document.documentElement.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'));"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<bindings xmlns="http://www.mozilla.org/xbl" id="b">
+ <binding id="foo">
+ <content><listitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><children xmlns="http://www.mozilla.org/xbl"/></listitem></content>
+ </binding>
+</bindings>
+
+<listbox id="l" style="-moz-binding: url(&quot;#foo&quot;);"><hbox id="h"/></listbox>
+
+<listitem/>
+
+</window>
diff --git a/layout/base/crashtests/522374-1.html b/layout/base/crashtests/522374-1.html
new file mode 100644
index 000000000..1dfbc2b81
--- /dev/null
+++ b/layout/base/crashtests/522374-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html><html class="reftest-wait"><head><script type="text/javascript">
+
+function boom()
+{
+ var area = document.getElementById("area");
+ var main = document.getElementById("main");
+
+ area.nextSibling.data += " a ";
+ document.documentElement.offsetHeight;
+ area.nextSibling.data = " b ";
+ main.previousSibling.data += " \u062A ";
+
+ document.documentElement.removeAttribute("class");
+}
+
+function boom0(ev)
+{
+ setTimeout(boom, 0);
+}
+
+</script></head><body onload="boom0();"> <div id="main" style="width: 1px;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC" usemap="#Map"><map name="Map"><area id="area"> </map></div></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/522374-2.html b/layout/base/crashtests/522374-2.html
new file mode 100644
index 000000000..934a6649d
--- /dev/null
+++ b/layout/base/crashtests/522374-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html><html class="reftest-wait"><head><script type="text/javascript">
+
+function boom()
+{
+ var area = document.getElementById("area");
+ var main = document.getElementById("main");
+
+ area.nextSibling.data += " a ";
+ document.documentElement.offsetHeight;
+ area.nextSibling.data = " b ";
+ main.previousSibling.data += " \u042A ";
+
+ document.documentElement.removeAttribute("class");
+}
+
+function boom0(ev)
+{
+ setTimeout(boom, 0);
+}
+
+</script></head><body onload="boom0();"> <div id="main" style="width: 1px;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC" usemap="#Map"><map name="Map"><area id="area"> </map></div></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/526378-1.xul b/layout/base/crashtests/526378-1.xul
new file mode 100644
index 000000000..0a5eaf808
--- /dev/null
+++ b/layout/base/crashtests/526378-1.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var x = document.getElementById("x");
+
+ x.appendChild(document.createTextNode("A"));
+ x.appendChild(document.createTextNode("\u202B" + "C"));
+
+ document.getBoxObjectFor(document.documentElement).height; // flush layout
+
+ x.normalize();
+ x.appendChild(document.createTextNode("D"));
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<box id="x" style="display:inline"><box/></box>
+</window>
diff --git a/layout/base/crashtests/534367-1.xhtml b/layout/base/crashtests/534367-1.xhtml
new file mode 100644
index 000000000..3e8de11c1
--- /dev/null
+++ b/layout/base/crashtests/534367-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+td:first-letter { }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var tbody = document.getElementById("tbody");
+ document.documentElement.offsetHeight;
+ tbody.style.direction = "rtl";
+ document.documentElement.offsetHeight;
+ tbody.style.direction = "";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+
+</head>
+<body onload="boom();">
+
+<table><tbody id="tbody"><tr><td><span>1 2</span></td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/534368-1.xhtml b/layout/base/crashtests/534368-1.xhtml
new file mode 100644
index 000000000..7f07902e3
--- /dev/null
+++ b/layout/base/crashtests/534368-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="position: absolute; font-size: 1000px; -moz-column-count: 3;">
+<script>
+
+function boom()
+{
+ var newSS = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ newSS.appendChild(document.createTextNode("whattheheck:first-line {}"));
+ document.getElementById("h").appendChild(newSS);
+}
+window.addEventListener("load", boom, false);
+
+</script>
+<head id="h" style="overflow-x: scroll; display: block;"><style style="position: absolute; display: block;">zz ]</style><style style="display: none;">[</style><style style="display: none;">[e='zz']:nth-last-child(odd) {}</style><style style="display: none;"></style><style style="display: none;">[c]</style><style style="display: none;">[c]</style><style style="display: none;">[class='zzzzz'] {}</style></head>
+</html>
diff --git a/layout/base/crashtests/534768-1.html b/layout/base/crashtests/534768-1.html
new file mode 100644
index 000000000..17c9ac68f
--- /dev/null
+++ b/layout/base/crashtests/534768-1.html
@@ -0,0 +1,23 @@
+<html style="direction: rtl;">
+<head>
+<style>
+
+body:after { content: '0'; }
+body:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.style.direction = "";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.textIndent = "3px";
+}
+
+</script>
+</head>
+
+<body onload="boom();"> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/534768-2.html b/layout/base/crashtests/534768-2.html
new file mode 100644
index 000000000..67ecb4b6b
--- /dev/null
+++ b/layout/base/crashtests/534768-2.html
@@ -0,0 +1,22 @@
+<html style="direction: rtl;">
+<head>
+<style>
+
+body:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.style.direction = "";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.textIndent = "3px";
+}
+
+</script>
+</head>
+
+<body onload="boom();"> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/535721-1.xhtml b/layout/base/crashtests/535721-1.xhtml
new file mode 100644
index 000000000..cd5696d30
--- /dev/null
+++ b/layout/base/crashtests/535721-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+
+ document.getElementById("i").appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "div"));
+
+}
+</script>
+</head>
+<body onload="boom();">
+
+<div><span><span id="i"><div></div></span></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/535911-1.xhtml b/layout/base/crashtests/535911-1.xhtml
new file mode 100644
index 000000000..593cfba4e
--- /dev/null
+++ b/layout/base/crashtests/535911-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var b = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box");
+ s.appendChild(b);
+ document.getElementById("a").appendChild(s);
+}
+
+</script>
+</head>
+<body onload="boom();" style="-moz-column-width: 1px;"><span id="a"><box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/></span><div>Hello</div></body>
+</html>
diff --git a/layout/base/crashtests/536623-1.xhtml b/layout/base/crashtests/536623-1.xhtml
new file mode 100644
index 000000000..b01e6a018
--- /dev/null
+++ b/layout/base/crashtests/536623-1.xhtml
@@ -0,0 +1,37 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <binding id="qwe">
+ <content>
+ <xul:label style="-moz-binding: url(#xar)" xbl:inherits="xbl:text=label" flex="1"/>
+ </content>
+ </binding>
+
+ <binding id="xar">
+ <content>
+ <html:table><children/></html:table>
+ </content>
+ </binding>
+
+</bindings>
+
+<script type="text/javascript">
+function boom()
+{
+ document.getElementById("b").setAttribute('label', "1 2 3");
+ document.documentElement.offsetHeight;
+ document.getElementById("b").removeAttribute('label');
+}
+</script>
+</head>
+
+<body onload="boom();">
+<div style="width: 0px;"><box id="b" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="-moz-binding: url(#qwe);"/></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/536720.xul b/layout/base/crashtests/536720.xul
new file mode 100644
index 000000000..2b8c0b614
--- /dev/null
+++ b/layout/base/crashtests/536720.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var menupopup = document.getElementById("menupopup");
+ menupopup.parentNode.removeChild(menupopup);
+}
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><menulist><menupopup id="menupopup"/>
+T
+</menulist></window>
+
+</window>
diff --git a/layout/base/crashtests/537059-1.xhtml b/layout/base/crashtests/537059-1.xhtml
new file mode 100644
index 000000000..16e0ab674
--- /dev/null
+++ b/layout/base/crashtests/537059-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="td"><content><html:div><children/></html:div></content></binding>
+ </bindings>
+ <style type="text/css">
+ div, tbody { position: absolute; }
+ div:first-letter { }
+ </style>
+ </head>
+ <body>
+ <div><table>A<tbody><tr style="-moz-binding: url(#td);"></tr></tbody>B</table></div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/537141-1.xhtml b/layout/base/crashtests/537141-1.xhtml
new file mode 100644
index 000000000..c9b3a7aba
--- /dev/null
+++ b/layout/base/crashtests/537141-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body onload="document.body.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'));" style="-moz-binding: url('537141.xml#mo');">
+<g xmlns="http://www.w3.org/2000/svg"/></body>
+</html>
diff --git a/layout/base/crashtests/537141.xml b/layout/base/crashtests/537141.xml
new file mode 100644
index 000000000..5e9ee8445
--- /dev/null
+++ b/layout/base/crashtests/537141.xml
@@ -0,0 +1,2 @@
+<!-- This has to be a separate file to trigger the bug -->
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="mo"><content><mrow xmlns="http://www.w3.org/1998/Math/MathML"><children xmlns="http://www.mozilla.org/xbl"/></mrow></content></binding></bindings>
diff --git a/layout/base/crashtests/537562-1.xhtml b/layout/base/crashtests/537562-1.xhtml
new file mode 100644
index 000000000..366c7ef64
--- /dev/null
+++ b/layout/base/crashtests/537562-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml" id="a">
+<head>
+<style>
+#a { -moz-column-count: 2; }
+#a:first-letter { }
+</style>
+</head>
+<body id="b" onload="document.getElementById('b').appendChild(document.createElement('tr'));"></body>
+X
+</html>
diff --git a/layout/base/crashtests/537624-1.html b/layout/base/crashtests/537624-1.html
new file mode 100644
index 000000000..a6c19516e
--- /dev/null
+++ b/layout/base/crashtests/537624-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ a.insertBefore(b, a.firstChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="a"><div></div></span><span id="b"><span style="display: none;"></span><span style="display: none;"></span></span></body>
+
+</html>
diff --git a/layout/base/crashtests/537631-1.html b/layout/base/crashtests/537631-1.html
new file mode 100644
index 000000000..d675837a7
--- /dev/null
+++ b/layout/base/crashtests/537631-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="position: fixed; -moz-column-count: 2;"><div style="position: absolute; height: 7em;"><br><br></div></body>
+</html>
diff --git a/layout/base/crashtests/538082-1.xul b/layout/base/crashtests/538082-1.xul
new file mode 100644
index 000000000..10335617e
--- /dev/null
+++ b/layout/base/crashtests/538082-1.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:m="http://www.w3.org/1998/Math/MathML">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var e = document.getElementById("e");
+ var g = document.getElementById("maligngroup");
+
+ var M = "http://www.w3.org/1998/Math/MathML";
+ var a = document.createElementNS(M, "mfrac");
+ var b = document.createElementNS(M, "ms");
+ var c = document.createElementNS(M, "merror");
+
+ g.appendChild(c);
+
+ a.appendChild(b);
+ e.appendChild(a);
+}
+
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<m:math><box id="e"><m:mo><m:ms/><box style="display: inline;"><box><m:maligngroup id="maligngroup"/></box></box></m:mo></box></m:math>
+
+</window>
diff --git a/layout/base/crashtests/538207-1.xhtml b/layout/base/crashtests/538207-1.xhtml
new file mode 100644
index 000000000..f893e2837
--- /dev/null
+++ b/layout/base/crashtests/538207-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("iframe").appendChild(document.createElement("span"));
+ document.getElementById("mrow").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+<body onload="boom();"><m:mrow id="mrow"><iframe id="iframe"/></m:mrow></body>
+</html>
diff --git a/layout/base/crashtests/538210-1.html b/layout/base/crashtests/538210-1.html
new file mode 100644
index 000000000..7070f8e99
--- /dev/null
+++ b/layout/base/crashtests/538210-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ var frameset = document.getElementsByTagName("frameset")[0]
+ var oldFrame = frameset.firstChild;
+ var newFrame = document.createElementNS("http://www.w3.org/1999/xhtml", "frame");
+ frameset.appendChild(newFrame);
+ frameset.removeChild(oldFrame);
+}
+</script>
+</head>
+<frameset onload="boom()"><frame></frame></frameset>
+</html>
+
diff --git a/layout/base/crashtests/538267-1.html b/layout/base/crashtests/538267-1.html
new file mode 100644
index 000000000..c89427974
--- /dev/null
+++ b/layout/base/crashtests/538267-1.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style>
+div:first-letter { float: left; }
+div { -moz-column-count: 2; width: 0; }
+</style>
+<script>
+function boom()
+{
+ var v = document.getElementById("v");
+ v.removeChild(v.firstChild);
+}
+</script>
+</head>
+<body onload="boom();">
+<div id="v">a b<span>c</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/540760.xul b/layout/base/crashtests/540760.xul
new file mode 100644
index 000000000..b0e857ec9
--- /dev/null
+++ b/layout/base/crashtests/540760.xul
@@ -0,0 +1,18 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function boom()
+{
+ var a = document.getElementById("a");
+ while (a.firstChild)
+ a.removeChild(a.firstChild);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<menulist id="a" sizetopopup="pref"><menupopup/><menupopup/></menulist>
+
+</window>
diff --git a/layout/base/crashtests/540771-1.xhtml b/layout/base/crashtests/540771-1.xhtml
new file mode 100644
index 000000000..3830e148d
--- /dev/null
+++ b/layout/base/crashtests/540771-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<xul:deck id="d"/>
+
+<xul:menuitem><span /><xul:menupopup id="p"/></xul:menuitem>
+
+<script>
+function boom()
+{
+ var p = document.getElementById("p");
+ var d = document.getElementById("d");
+ p.parentNode.removeChild(p);
+ d.appendChild(document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box"));
+}
+window.addEventListener("load", boom, false);
+</script>
+
+</html>
diff --git a/layout/base/crashtests/541869-1.xhtml b/layout/base/crashtests/541869-1.xhtml
new file mode 100644
index 000000000..b25c2b1f3
--- /dev/null
+++ b/layout/base/crashtests/541869-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="right: 10000px; top: 11121164%; position: fixed;"><div>X</div>
+<script>
+window.addEventListener("load", function() { document.documentElement.style.letterSpacing = '21em'; }, false);
+</script>
+</html>
diff --git a/layout/base/crashtests/541869-2.html b/layout/base/crashtests/541869-2.html
new file mode 100644
index 000000000..2800150db
--- /dev/null
+++ b/layout/base/crashtests/541869-2.html
@@ -0,0 +1,5 @@
+<html style="padding: 9007199254740991%;">
+<body onload="document.getElementById('f').style.border = 'none';" style="display: inline">
+<iframe id="f"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/543648-1.html b/layout/base/crashtests/543648-1.html
new file mode 100644
index 000000000..dff9440d1
--- /dev/null
+++ b/layout/base/crashtests/543648-1.html
@@ -0,0 +1 @@
+<html style="-moz-border-top-colors: red; border: 10000000px solid yellow;"><body></body></html>
diff --git a/layout/base/crashtests/559705.xhtml b/layout/base/crashtests/559705.xhtml
new file mode 100644
index 000000000..7f58978ec
--- /dev/null
+++ b/layout/base/crashtests/559705.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content>
+ <iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:void 0"><children xmlns="http://www.mozilla.org/xbl"/></iframe>
+ </content>
+ </binding>
+ </bindings>
+</head>
+<body>
+ <span style="-moz-binding: url(#foo)"></span>
+</body>
+</html>
diff --git a/layout/base/crashtests/560441-1.xhtml b/layout/base/crashtests/560441-1.xhtml
new file mode 100644
index 000000000..bc3b6b503
--- /dev/null
+++ b/layout/base/crashtests/560441-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><frame xmlns="http://www.w3.org/1999/xhtml"><children xmlns="http://www.mozilla.org/xbl"/></frame></content></binding></bindings>
+</head>
+
+<frameset
+ onload="document.getElementById('y').appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'));"
+ style="-moz-binding: url(#foo)"
+>
+<frame id="y"></frame>
+</frameset>
+</html>
diff --git a/layout/base/crashtests/560447-1.html b/layout/base/crashtests/560447-1.html
new file mode 100644
index 000000000..e6d4f9cb4
--- /dev/null
+++ b/layout/base/crashtests/560447-1.html
@@ -0,0 +1 @@
+<html><body onload="setTimeout(function(){document.getElementById('m').appendChild(document.createElement('area'));},0);"><map id="m" name="m"></map><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#m"></body></html>
diff --git a/layout/base/crashtests/564063-1.html b/layout/base/crashtests/564063-1.html
new file mode 100644
index 000000000..eb288982e
--- /dev/null
+++ b/layout/base/crashtests/564063-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.getElementById("m"));
+ document.getElementsByTagName("area")[0].appendChild(document.createTextNode('x'));
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 200);">
+
+<map name="m" id="m"><area></map><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#m">
+
+</body>
+</html>
diff --git a/layout/base/crashtests/567292-1.xhtml b/layout/base/crashtests/567292-1.xhtml
new file mode 100644
index 000000000..35515c389
--- /dev/null
+++ b/layout/base/crashtests/567292-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><optgroup><span><children xmlns="http://www.mozilla.org/xbl"/></span></optgroup></content></binding></bindings>
+
+<script>
+<![CDATA[
+
+function boom() { document.getElementById("a").appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "span")); }
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script></head>
+
+<frameset style="-moz-binding: url(&quot;#foo&quot;);"><frame id="a"></frame></frameset>
+
+</html>
diff --git a/layout/base/crashtests/56746-1.html b/layout/base/crashtests/56746-1.html
new file mode 100644
index 000000000..83215467d
--- /dev/null
+++ b/layout/base/crashtests/56746-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>Example 8</title>
+</head>
+<body>
+<FORM METHOD="GET" ACTION="" NAME="searchform">
+ <BUTTON type=submit >
+ <table>
+ <tr>
+ <td>CELL 1</td>
+ </tr>
+ </table>
+ </BUTTON>
+</FORM>
+</body>
+</html>
diff --git a/layout/base/crashtests/569018-1.html b/layout/base/crashtests/569018-1.html
new file mode 100644
index 000000000..557787f66
--- /dev/null
+++ b/layout/base/crashtests/569018-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("x").appendChild(document.getElementsByTagName("map")[0]);
+ document.getElementsByTagName("area")[0].appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", 'span'));
+ document.body.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", 'td'));
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);"><img src="data:image/gif,GIF87a%02%00%02%00%B3%00%00%00%00%00%FF%FF%FF%00%00%00%00%00%00%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%2C%00%00%00%00%02%00%02%00%00%04%03%90H%12%00%3B" usemap="#Map"><map name="Map"><area></map><span id="x"></span></body>
+</html>
diff --git a/layout/base/crashtests/570038-1.html b/layout/base/crashtests/570038-1.html
new file mode 100644
index 000000000..93fd2b993
--- /dev/null
+++ b/layout/base/crashtests/570038-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body><div style="display: -moz-deck;"><div style="overflow: auto; border: 4294967296px solid blue;">M</div></div></body>
+</html>
diff --git a/layout/base/crashtests/572003.xul b/layout/base/crashtests/572003.xul
new file mode 100644
index 000000000..10488fc64
--- /dev/null
+++ b/layout/base/crashtests/572003.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var p=document.getElementById('p'); for(var i=0;i!=3;++i)p.parentNode.appendChild(p);">
+<menuitem style="counter-reset: chicken;">P<popup id="p" style="counter-reset: chicken;"/></menuitem>
+</window>
diff --git a/layout/base/crashtests/572582-1.xhtml b/layout/base/crashtests/572582-1.xhtml
new file mode 100644
index 000000000..d21096e88
--- /dev/null
+++ b/layout/base/crashtests/572582-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="width: 1px">
+<head>
+<script style="display: none;" id="fuzz1" type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var span = document.createElementNS("http://www.w3.org/1999/xhtml", "span")
+ var t1 = document.createTextNode("\uD1B5");
+ span.appendChild(t1);
+ var t2 = document.createTextNode("");
+ span.appendChild(t2);
+ var t3 = document.createTextNode("\u200Bq");
+ span.appendChild(t3);
+ document.documentElement.appendChild(span);
+ document.documentElement.offsetHeight;
+ t3.data = "\u062A";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/576649-1.html b/layout/base/crashtests/576649-1.html
new file mode 100644
index 000000000..437ea93b5
--- /dev/null
+++ b/layout/base/crashtests/576649-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('p').style.fontWeight = 'bold';" style="letter-spacing: 2251799813685247pt; -moz-column-count: 11; position: absolute;"><p id="p"><span style="position: absolute;">C d s</span></p></body>
+</html>
diff --git a/layout/base/crashtests/579655.html b/layout/base/crashtests/579655.html
new file mode 100644
index 000000000..460fa34c2
--- /dev/null
+++ b/layout/base/crashtests/579655.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<script type="text/javascript">
+function load()
+{
+ var bs=document.getElementById("b");
+ if(bs.lastChild){
+ bs.lastChild.textContent="text\/css";
+ bs.textContent="head";
+ }
+}
+</script>
+<body onload="load();">
+ <table >
+ <td>
+ <p id="b">aaaa aaaa aaa aa aaaa aa aaaaaa aaaa aa aa aaa aaa aaaa aaaaaaa aaa aaaa aa a aaaa aaaa aaaaaaa aa aa aa aa aaaaaaa .</p>
+ <body style="white-space:pre-wrap" >
+ </body>
+ </td>
+ <td dir="rtl" >
+ <body contenteditable="true">
+ </body>
+ </td>
+ </table>
+</body>
+</html>
diff --git a/layout/base/crashtests/580129-1.html b/layout/base/crashtests/580129-1.html
new file mode 100644
index 000000000..228051b5a
--- /dev/null
+++ b/layout/base/crashtests/580129-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.documentElement;
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ b.setAttributeNS(null, "style", "-moz-column-width: 20em;");
+ a.innerHTML = "<frameset>";
+ b.innerHTML = "<dd><marquee>x";
+ document.removeChild(a);
+ document.appendChild(b);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/580494-1.html b/layout/base/crashtests/580494-1.html
new file mode 100644
index 000000000..c76125f74
--- /dev/null
+++ b/layout/base/crashtests/580494-1.html
@@ -0,0 +1 @@
+<html><body><marquee><video></video></marquee></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/580834-1.xhtml b/layout/base/crashtests/580834-1.xhtml
new file mode 100644
index 000000000..0a61c5201
--- /dev/null
+++ b/layout/base/crashtests/580834-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<menuitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><tooltip/><vbox xmlns="http://www.w3.org/1999/xhtml"></vbox></menuitem>
+</body>
+</html>
diff --git a/layout/base/crashtests/589787.html b/layout/base/crashtests/589787.html
new file mode 100644
index 000000000..558a14083
--- /dev/null
+++ b/layout/base/crashtests/589787.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom() {
+ document.documentElement.offsetHeight;
+ document.getElementById('e').setAttribute('style', '');
+ document.documentElement.offsetHeight;
+}
+</script>
+<style id="e">
+body #a::after { content: "before text"; position: fixed; }
+</style>
+</head>
+
+<body onload="boom();" style="-moz-column-count: 2; width: 100px;">
+<div>m</div>
+<div id="a" style="-moz-column-count: 2;">
+m
+<br style="float: left;">
+m
+<span style="float: left;">m</span>
+
+<div style="float: left; -moz-column-width: 9999999999px;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/591075-1.html b/layout/base/crashtests/591075-1.html
new file mode 100644
index 000000000..7804f530f
--- /dev/null
+++ b/layout/base/crashtests/591075-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="max-width: -moz-max-content"><body style="max-width: 210708270904025mozmm"></body></html>
diff --git a/layout/base/crashtests/591998-1.html b/layout/base/crashtests/591998-1.html
new file mode 100644
index 000000000..ac461dab8
--- /dev/null
+++ b/layout/base/crashtests/591998-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="border: 168691114px solid green"><body ></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/595039-1.html b/layout/base/crashtests/595039-1.html
new file mode 100644
index 000000000..775eeca0e
--- /dev/null
+++ b/layout/base/crashtests/595039-1.html
@@ -0,0 +1 @@
+<html><body><div style="height: 100px; background-image: -moz-linear-gradient(left top , yellow, blue); background-size: 4398046511104mozmm;"></div></body></html>
diff --git a/layout/base/crashtests/597924-1.html b/layout/base/crashtests/597924-1.html
new file mode 100644
index 000000000..d855997ee
--- /dev/null
+++ b/layout/base/crashtests/597924-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("s").appendChild(document.createElement("div"));
+ var marq = document.getElementById("f").contentDocument.documentElement;
+ marq.behavior = "alternate";
+}
+
+</script>
+</head>
+<body onload="boom();"><span id="s"></span><iframe src="data:text/xml,%3Cmarquee%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3EX%3C%2Fmarquee%3E" id="f"></iframe></body>
+</html>
diff --git a/layout/base/crashtests/606432-1.html b/layout/base/crashtests/606432-1.html
new file mode 100644
index 000000000..a166c94de
--- /dev/null
+++ b/layout/base/crashtests/606432-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var t = document.getElementById("f").contentDocument.documentElement;
+ t.contentEditable = "true";
+ t.focus();
+ document.body.appendChild(t);
+ setTimeout(finish, 0);
+}
+
+function finish()
+{
+ document.documentElement.className = "";
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 200);"><iframe id="f" src="data:application/xhtml+xml,<html xmlns='http://www.w3.org/1999/xhtml'></html>"></iframe></body>
+</html>
diff --git a/layout/base/crashtests/609821-1.xhtml b/layout/base/crashtests/609821-1.xhtml
new file mode 100644
index 000000000..bd2feb9c0
--- /dev/null
+++ b/layout/base/crashtests/609821-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function boom()
+{
+ var td = document.getElementById("td");
+ td.contentEditable = "true";
+ td.focus();
+}
+
+]]>
+</script></head>
+
+<body onload="boom();"><td id="td"/></body>
+</html>
diff --git a/layout/base/crashtests/613817-1.svg b/layout/base/crashtests/613817-1.svg
new file mode 100644
index 000000000..9f5165ffe
--- /dev/null
+++ b/layout/base/crashtests/613817-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg"><foreignObject id="fo">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: -moz-popup;"></div></foreignObject><script>
+
+function boom()
+{
+ document.getElementById("fo").style.MozAppearance = "menuitem";
+}
+
+window.addEventListener("load", boom, false);
+
+</script></svg>
+
diff --git a/layout/base/crashtests/615146-1.html b/layout/base/crashtests/615146-1.html
new file mode 100644
index 000000000..b7b3cb279
--- /dev/null
+++ b/layout/base/crashtests/615146-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><svg requiredExtensions=e><foreignObject>
diff --git a/layout/base/crashtests/615781-1.xhtml b/layout/base/crashtests/615781-1.xhtml
new file mode 100644
index 000000000..1c0b53c6b
--- /dev/null
+++ b/layout/base/crashtests/615781-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<input id="i"/>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ var i = document.getElementById("i");
+ i.select();
+ i.setAttribute("type", "radio");
+ i.blur();
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function() { setTimeout(boom, 100); }, false);
+
+]]>
+</script>
+
+</html>
diff --git a/layout/base/crashtests/616495-single-side-composite-color-border.html b/layout/base/crashtests/616495-single-side-composite-color-border.html
new file mode 100644
index 000000000..13bcad030
--- /dev/null
+++ b/layout/base/crashtests/616495-single-side-composite-color-border.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title> Composite Color Crash Test </title>
+
+ <style>
+ .kaboom {
+ margin: 100px;
+ border-width: 20px 20px 20px 20px;
+ -moz-border-top-colors: green red green;
+ border-style: solid;
+ width: 70px;
+ height: 70px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div id="box" class="kaboom"></div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/629035-1.html b/layout/base/crashtests/629035-1.html
new file mode 100644
index 000000000..ef1cfafae
--- /dev/null
+++ b/layout/base/crashtests/629035-1.html
@@ -0,0 +1,3 @@
+<script>
+ document.dir = "rtl";
+</script>
diff --git a/layout/base/crashtests/629908-1.html b/layout/base/crashtests/629908-1.html
new file mode 100644
index 000000000..49b978597
--- /dev/null
+++ b/layout/base/crashtests/629908-1.html
@@ -0,0 +1,9 @@
+<body onload="die()">
+ <script>
+ function die() {
+ document.body.offsetWidth;
+ document.removeChild(document.documentElement);
+ document.dir = "rtl";
+ }
+ </script>
+</body>
diff --git a/layout/base/crashtests/635329.html b/layout/base/crashtests/635329.html
new file mode 100644
index 000000000..15153bda2
--- /dev/null
+++ b/layout/base/crashtests/635329.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="margin-left: 100%">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.lastChild.data = "\u062A"; // ARABIC LETTER TEH
+ document.body.lastChild.data += "Y";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span>A</span> B C</body>
+</html>
diff --git a/layout/base/crashtests/636229-1.html b/layout/base/crashtests/636229-1.html
new file mode 100644
index 000000000..e09e22ef2
--- /dev/null
+++ b/layout/base/crashtests/636229-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="clip-path: url(&quot;#404&quot;); overflow: -moz-hidden-unscrollable;"><body style="height: 400px; outline: 171787972850px solid green;"></body></html>
diff --git a/layout/base/crashtests/640272-empty.html b/layout/base/crashtests/640272-empty.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/layout/base/crashtests/640272-empty.html
diff --git a/layout/base/crashtests/640272-ref.html b/layout/base/crashtests/640272-ref.html
new file mode 100644
index 000000000..951c0ae4b
--- /dev/null
+++ b/layout/base/crashtests/640272-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 640272</title>
+<style>
+#waBackButton {
+ border: 1px solid blue;
+}
+</style>
+</head>
+<body>
+ <a href="index.html" id="waBackButton">Indietro</a>
+</body>
+</html>
diff --git a/layout/base/crashtests/640272.html b/layout/base/crashtests/640272.html
new file mode 100644
index 000000000..0df1df96a
--- /dev/null
+++ b/layout/base/crashtests/640272.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 640272</title>
+<style>
+#waBackButton {
+ border: 1px solid blue;
+ border-image: url(640272-empty.html) 0 10 0 15;
+}
+</style>
+</head>
+<body>
+ <a href="index.html" id="waBackButton">Indietro</a>
+</body>
+</html>
diff --git a/layout/base/crashtests/645193.html b/layout/base/crashtests/645193.html
new file mode 100644
index 000000000..86d2a30d9
--- /dev/null
+++ b/layout/base/crashtests/645193.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="direction: rtl; -moz-column-width: 1px;"><head><script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.style.unicodeBidi = "bidi-override";
+ document.documentElement.offsetHeight;
+}
+
+</script></head><body style="white-space: pre;" onload="boom();">
+H
+
+
+</body></html>
diff --git a/layout/base/crashtests/645572-1.html b/layout/base/crashtests/645572-1.html
new file mode 100644
index 000000000..46dd22ad3
--- /dev/null
+++ b/layout/base/crashtests/645572-1.html
@@ -0,0 +1,52 @@
+<html class="reftest-wait">
+<script>
+function start(){
+ tmp=document.createElement('iframe');
+ tmp.id='ifr32247';
+ tmp.addEventListener("load", start_dataiframe9, false);
+ document.documentElement.appendChild(tmp);
+}function start_dataiframe9(){
+ o185=document.getElementById('ifr32247').contentDocument.documentElement;
+ tmp=document.createElement('iframe')
+ o196=document.getElementById('ifr32247').contentDocument.createElementNS('http:2000svg','altGlyph');
+ o230=o185.cloneNode(true);
+ tmp.id='ifr42257';
+ o230.ownerDocument.documentElement.appendChild(tmp);
+ start_dataiframe11();
+ //window.setTimeout('start_dataiframe11()',100);
+}function start_dataiframe11(){
+ o232=o230.ownerDocument.getElementById('ifr42257').contentDocument.documentElement;
+ o234=o196;
+ tmp=o234.ownerDocument.createElement('iframe');
+ tmp.src='data:text/html,' + escape("<q id='element2'><q id='element3'><q id='element4'><dd style id='element6'>");
+ tmp.id='ifr22371';
+ tmp.addEventListener("load", start_dataiframe12, false);
+ o234.ownerDocument.documentElement.appendChild(tmp);
+}function start_dataiframe12(){
+ o239=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element2');
+ o240=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element3');
+ o241=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element4');
+ o243=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element6');
+ o232.addEventListener('DOMNodeRemoved',function(){this.offsetHeight;},true);
+ o272=o185.cloneNode(false);
+ o232.innerHTML=unescape("%3Cxmp%3E20style3E");
+ o276=document.createTextNode('window;');
+ o278=document.createTextNode('o243className=1;');
+ o243.innerHTML=unescape('22%3Cform%3E');
+ o232.appendChild(o241);
+ o288=o240.cloneNode(true);
+ o185.appendChild(o288);
+ o241.innerHTML='<input placeholder>';
+ o241.style.position='absolute';
+ o232.style.cssText='opacity:0;display:table;';
+ o241.appendChild(o276);
+ o241.appendChild(o239);
+ o241.offsetParent.appendChild(o243);
+ o288.appendChild(o272);
+ o240.appendChild(o276);
+ o241.offsetParent.appendChild(o278);
+ document.documentElement.removeAttribute("class");
+}
+addEventListener("load", start, false);
+</script>
+</html>
diff --git a/layout/base/crashtests/650475.xhtml b/layout/base/crashtests/650475.xhtml
new file mode 100644
index 000000000..69d171b2d
--- /dev/null
+++ b/layout/base/crashtests/650475.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.body.offsetHeight;
+ document.body.appendChild(document.createTextNode('Y'));
+}
+
+</script>
+</head>
+<body style="white-space: pre;" onload="boom();">&#x000D;&#x064C;</body>
+</html>
diff --git a/layout/base/crashtests/650489.xhtml b/layout/base/crashtests/650489.xhtml
new file mode 100644
index 000000000..b9270d570
--- /dev/null
+++ b/layout/base/crashtests/650489.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;"><body style="-moz-column-width: 1px; word-wrap: break-word; white-space: pre-wrap;" onload="document.documentElement.offsetHeight; document.body.style.wordWrap='';">
+
+xy</body></html>
diff --git a/layout/base/crashtests/651342-1.html b/layout/base/crashtests/651342-1.html
new file mode 100644
index 000000000..a2851268b
--- /dev/null
+++ b/layout/base/crashtests/651342-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="position: relative; bottom: 2305843009213694000mozmm; float: left; border-bottom-style: solid;">r</body>
+</html>
diff --git a/layout/base/crashtests/653133-1.html b/layout/base/crashtests/653133-1.html
new file mode 100644
index 000000000..d0de0585f
--- /dev/null
+++ b/layout/base/crashtests/653133-1.html
@@ -0,0 +1,17 @@
+<html reftest-displayport-w="800" reftest-displayport-h="4096">
+<head>
+<style type="text/css">
+body
+{
+background-image:url("");
+background-attachment:fixed;
+}
+</style>
+</head>
+
+<body>
+<div style="height: 100000px">
+<h1>background-attachment:fixed crashtest</h1>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/663295.html b/layout/base/crashtests/663295.html
new file mode 100644
index 000000000..377f587e0
--- /dev/null
+++ b/layout/base/crashtests/663295.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html><html style="font-size-adjust: 193373343913878; white-space: pre-line;"><body onload="document.documentElement.style.MozColumnGap='1px';"><span>A B C
+&#x062A;</span></body></html>
diff --git a/layout/base/crashtests/663662-1.html b/layout/base/crashtests/663662-1.html
new file mode 100644
index 000000000..0dcba5679
--- /dev/null
+++ b/layout/base/crashtests/663662-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head></head><body onload="document.documentElement.offsetHeight; document.body.style.MozColumnWidth='40000px';" style="word-spacing: 200000px; font-size-adjust: 2000; direction: rtl; white-space: pre-wrap; width: 50000px; -moz-column-width: 1px; height: 5000px;"> &#x00A0;&#x000D;&#x0001;X&#x4372;Y </body></html>
diff --git a/layout/base/crashtests/663662-2.html b/layout/base/crashtests/663662-2.html
new file mode 100644
index 000000000..0aab12a79
--- /dev/null
+++ b/layout/base/crashtests/663662-2.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head></head><body onload="document.documentElement.offsetHeight; document.body.style.MozColumnWidth='40000px';" style="word-spacing: 200000px; font-size-adjust: 2000; direction: rtl; white-space: pre-wrap; width: 50000px; -moz-column-width: 1px; height: 5000px;"> &#x00A0;&#x000A;&#x0001;X&#x4372;Y </body></html>
diff --git a/layout/base/crashtests/665837.html b/layout/base/crashtests/665837.html
new file mode 100644
index 000000000..df58b2802
--- /dev/null
+++ b/layout/base/crashtests/665837.html
@@ -0,0 +1,13 @@
+<html style="direction: rtl; -moz-column-width: 0pt; white-space: pre-line;"><head><script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.documentElement.style.fontSize = "200%";
+ document.documentElement.offsetHeight;
+}
+
+</script></head><body onload="boom();">
+
+A B
+C</body></html>
diff --git a/layout/base/crashtests/668579.html b/layout/base/crashtests/668579.html
new file mode 100644
index 000000000..da53822c2
--- /dev/null
+++ b/layout/base/crashtests/668579.html
@@ -0,0 +1,10 @@
+<html><head></head><body>
+<script>
+document.body.setAttribute('style', 'position: fixed; -moz-transition-duration: 1s;-moz-transform: scale(1.5);');
+</script>
+</body>
+</html>
+
+
+
+
diff --git a/layout/base/crashtests/668941.xhtml b/layout/base/crashtests/668941.xhtml
new file mode 100644
index 000000000..a1547a6b0
--- /dev/null
+++ b/layout/base/crashtests/668941.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="width: 1px; white-space: pre-wrap;">y
+<style>
+
+html:first-letter { }
+
+</style><script>
+
+window.addEventListener("load", function(){
+ document.documentElement.offsetHeight;
+ document.documentElement.style.direction = "rtl";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.margin = "3em";
+ document.documentElement.offsetHeight;
+}, false);
+
+</script></html>
diff --git a/layout/base/crashtests/670226.html b/layout/base/crashtests/670226.html
new file mode 100644
index 000000000..120790520
--- /dev/null
+++ b/layout/base/crashtests/670226.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>body:first-letter { float: left; }</style>
+</head>
+
+<body style="white-space: pre-line;">&#x062A;
+</body>
+
+</html>
diff --git a/layout/base/crashtests/675246-1.xhtml b/layout/base/crashtests/675246-1.xhtml
new file mode 100644
index 000000000..c24591c46
--- /dev/null
+++ b/layout/base/crashtests/675246-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-print">
+<style><![CDATA[
+ tfoot::after { content: "m"; position: fixed;}
+]]>
+</style>
+<td></td>
+<tfoot style="page-break-before: always;"></tfoot>
+</html>
diff --git a/layout/base/crashtests/690247-1.html b/layout/base/crashtests/690247-1.html
new file mode 100644
index 000000000..8f9d9e96f
--- /dev/null
+++ b/layout/base/crashtests/690247-1.html
@@ -0,0 +1,2 @@
+<html style="mask: url(&quot;#b&quot;);"><div style="overflow-x: scroll; overflow-y: scroll; font-size-adjust: 600"><math xmlns="http://www.w3.org/1998/Math/MathML"><mo>x</mo></math></div></html>
+
diff --git a/layout/base/crashtests/690619-1.html b/layout/base/crashtests/690619-1.html
new file mode 100644
index 000000000..9b2c40641
--- /dev/null
+++ b/layout/base/crashtests/690619-1.html
@@ -0,0 +1 @@
+<html style="background: -moz-element(#e);"><body><table><colgroup id="e"></colgroup></table></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/691118-1.html b/layout/base/crashtests/691118-1.html
new file mode 100644
index 000000000..23174656f
--- /dev/null
+++ b/layout/base/crashtests/691118-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("x").style.counterIncrement = "a";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+
+<body onload="boom();" style="-moz-column-count: 3">
+ <div style="position: relative;">
+ <div style="position: absolute; height: 3pt;"></div>
+ <div style="position: absolute;" id="x"></div>
+ <div style="position: absolute; height: 8pt;"></div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/695861.html b/layout/base/crashtests/695861.html
new file mode 100644
index 000000000..f37164d86
--- /dev/null
+++ b/layout/base/crashtests/695861.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.documentElement.offsetHeight; document.getElementById('s').style.textTransform='uppercase'; document.documentElement.offsetHeight; ">
+
+<div style="white-space: pre-wrap; -moz-column-count: 2;"><span id="s" style="unicode-bidi: isolate;">
+ <div style="direction: rtl;"></div></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/695964-1.svg b/layout/base/crashtests/695964-1.svg
new file mode 100644
index 000000000..c61ee10da
--- /dev/null
+++ b/layout/base/crashtests/695964-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="-moz-transform-style: preserve-3d"><foreignObject/></svg>
diff --git a/layout/base/crashtests/698335.html b/layout/base/crashtests/698335.html
new file mode 100644
index 000000000..d0901cf2f
--- /dev/null
+++ b/layout/base/crashtests/698335.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html><html style="white-space: pre-wrap; direction: rtl; -moz-column-width: 1px;"><style style="display: none;">.fl:first-letter { }</style><body class="fl">&#xD288;&#x062A;
+D</body></html>
diff --git a/layout/base/crashtests/699353-1.html b/layout/base/crashtests/699353-1.html
new file mode 100644
index 000000000..65e7251ab
--- /dev/null
+++ b/layout/base/crashtests/699353-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script>
+
+function boom()
+{
+ document.execCommand("inserthtml", false, "ABC ");
+ document.execCommand("delete", false, null);
+ document.execCommand("inserthtml", false, "<style>");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);" contenteditable="true"></body>
+</html>
diff --git a/layout/base/crashtests/701504.html b/layout/base/crashtests/701504.html
new file mode 100644
index 000000000..d2b95be60
--- /dev/null
+++ b/layout/base/crashtests/701504.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+
+ var x = document.getElementById('x');
+ x.removeChild(x.childNodes[1]);
+
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div style="-moz-column-count: 2;"><span style="unicode-bidi: isolate;" id="x"><span style="direction: rtl;"></span> <span style="unicode-bidi: isolate; white-space: pre;">
+x</span></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/707098.html b/layout/base/crashtests/707098.html
new file mode 100644
index 000000000..3f89ee7fb
--- /dev/null
+++ b/layout/base/crashtests/707098.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="var x = document.getElementById('x'); x.parentNode.removeChild(x);">
+<div><bdi><bdi><span id="x">&#x062A;</span> </bdi></bdi></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/709536-1.xhtml b/layout/base/crashtests/709536-1.xhtml
new file mode 100644
index 000000000..6d67114b8
--- /dev/null
+++ b/layout/base/crashtests/709536-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="border-spacing: 300px; -moz-column-width: 0px;">h<body style="-moz-column-count: 1;"><td></td><textarea style="float: left;"></textarea></body></html>
diff --git a/layout/base/crashtests/722137.html b/layout/base/crashtests/722137.html
new file mode 100644
index 000000000..7dae47f1d
--- /dev/null
+++ b/layout/base/crashtests/722137.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="width: 1px">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var x = document.getElementById("x").firstChild;
+ x.data = "a" + x.data;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="x">
+&#x202a;&#x10871;</span></body>
+</html>
diff --git a/layout/base/crashtests/725535.html b/layout/base/crashtests/725535.html
new file mode 100644
index 000000000..b0d504e5a
--- /dev/null
+++ b/layout/base/crashtests/725535.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html style="-moz-transform-style: preserve-3d">
+<body>
+<script>
+document.addEventListener("MozReftestInvalidate", function() {document.documentElement.style.MozTransform = 'rotate(0)';}, false);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/727601.html b/layout/base/crashtests/727601.html
new file mode 100644
index 000000000..cc6ef390b
--- /dev/null
+++ b/layout/base/crashtests/727601.html
@@ -0,0 +1,3 @@
+<html style="display: table; -moz-transform: scalex(10);">
+<body><script>document.addEventListener("MozReftestInvalidate", function() {document.documentElement.style.MozTransform = 'scalex(20)';})</script></body>
+</html>
diff --git a/layout/base/crashtests/735943.html b/layout/base/crashtests/735943.html
new file mode 100644
index 000000000..92b58f9de
--- /dev/null
+++ b/layout/base/crashtests/735943.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+var asvg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><script xlink:href="data:text/javascript," /><rect width="100" height="100" fill="green"><set attributeName="fill" attributeType="CSS" to="red" begin="0s" end="2s" dur="2s" fill="remove" /></rect></svg>';
+
+function boom()
+{
+ var f = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); f.src = "data:text/html,1"; document.body.appendChild(f);
+ var w;
+
+ setTimeout(function() {
+ w = window.open("data:text/html,<body onload=window.close()>", "_blank", "width=200,height=200");
+ // Note that most of the code below will execute before the window appears, and in fact before "w" becomes non-null.
+ }, 0);
+
+ setTimeout(function() {
+ setTimeout(function() { }, 0);
+ f.contentWindow.location = "data:image/svg+xml," + encodeURIComponent(asvg);
+
+ setTimeout(function() {
+ setTimeout(function() {
+ setTimeout(function() {
+ document.body.style.MozColumnCount = "2";
+ document.documentElement.className = "";
+ }, 20);
+ }, 0);
+ }, 0);
+ }, 20);
+}
+
+ window.addEventListener("MozReftestInvalidate", boom, false);
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/layout/base/crashtests/736389-1.xhtml b/layout/base/crashtests/736389-1.xhtml
new file mode 100644
index 000000000..530395f93
--- /dev/null
+++ b/layout/base/crashtests/736389-1.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+/* this stylesheet is reduced from quirk.css */
+
+li {
+ list-style-position: inside;
+}
+
+.t:first-child {
+ padding-top: 1em;
+}
+
+</style>
+
+<script>
+
+function rm(n) { n.parentNode.removeChild(n); }
+
+window.addEventListener("load", function() {
+ document.documentElement.offsetHeight;
+ rm(document.getElementById('x'));
+}, false);
+
+</script>
+</head>
+
+<body style="-moz-column-count: 2000;">
+ <li>
+ <ol class="t" style="position: relative;">
+ <span id="x"></span>
+ <ol class="t" style="list-style-position: inside;">
+ <div style="position: absolute;">
+ <li>
+ <div style="position: absolute;">
+ <li>
+ <ol class="t"></ol>
+ </li>
+ </div>
+ </li>
+ </div>
+ </ol>
+ </ol>
+ </li>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/736924-1.html b/layout/base/crashtests/736924-1.html
new file mode 100644
index 000000000..b9274bd78
--- /dev/null
+++ b/layout/base/crashtests/736924-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<script>
+function boom()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var x = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var y = document.createElementNS("http://www.w3.org/1999/xhtml", "basefont");
+ var z = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ z.setAttributeNS(null, "link", "#333333");
+
+ document.documentElement.appendChild(a);
+ b.appendChild(x);
+ b.appendChild(y);
+ document.documentElement.offsetHeight;
+ a.appendChild(b);
+ document.documentElement.offsetHeight;
+ document.createElementNS("http://www.w3.org/1999/xhtml", "div").appendChild(y);
+ b.appendChild(z);
+}
+</script>
+<body onload="boom();"></body>
diff --git a/layout/base/crashtests/749816-1.html b/layout/base/crashtests/749816-1.html
new file mode 100644
index 000000000..125553886
--- /dev/null
+++ b/layout/base/crashtests/749816-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>crash in epoll_wait after changing display: table-column style to display:none on body</title>
+<script>
+function doe() {
+document.body.style.display = 'none';
+}
+setTimeout(doe, 1000);
+</script>
+</head>
+
+<body style="display: table-column;">
+This page should not crash Fennec
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/763223-1.html b/layout/base/crashtests/763223-1.html
new file mode 100644
index 000000000..e970bb8ae
--- /dev/null
+++ b/layout/base/crashtests/763223-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body style="position: relative; padding-right: 59729800px;" onload="document.documentElement.offsetHeight; document.getElementById('x').style.right = '100px';">
+<div id="x" style="position: absolute; width: -moz-fit-content; height: 3px;"></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/763702.xhtml b/layout/base/crashtests/763702.xhtml
new file mode 100644
index 000000000..37e9fc5e6
--- /dev/null
+++ b/layout/base/crashtests/763702.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Bug 763702 - crash in nsFontInflationData::FindFontInflationDataFor at crash address 0x28 (((nsIFrame*)0)->GetStateBits())</title>
+ </head>
+ <div>parseerror, this should not cause Fennec to crash
+
+</html>
diff --git a/layout/base/crashtests/767593-1.html b/layout/base/crashtests/767593-1.html
new file mode 100644
index 000000000..38dc60e83
--- /dev/null
+++ b/layout/base/crashtests/767593-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<body>
+<div><span style="page-break-after: always;"></span><div style="position: fixed;"><span style="display: none;"></span></div>B</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/767593-2.html b/layout/base/crashtests/767593-2.html
new file mode 100644
index 000000000..b89b3ea90
--- /dev/null
+++ b/layout/base/crashtests/767593-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<body>
+<div><span style="page-break-after: always;"></span><div style="position: fixed;"><span style="display: none;"></span><span style="display: none;"></span></div>B</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/770381-1.html b/layout/base/crashtests/770381-1.html
new file mode 100644
index 000000000..85528a81c
--- /dev/null
+++ b/layout/base/crashtests/770381-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style id="s"></style>
+<script>
+function boom() { document.getElementById("s").textContent = "div { opacity: 0.5; }"; }
+</script>
+</head>
+<body onload="document.documentElement.offsetHeight; boom();">
+<div><div>X</div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/772306.html b/layout/base/crashtests/772306.html
new file mode 100644
index 000000000..01ac5ba8d
--- /dev/null
+++ b/layout/base/crashtests/772306.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var allNodes = [];
+ allNodes[5] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[5].style.setProperty("-moz-column-width", "200px", "");
+ allNodes[5].style.setProperty("height", "2em", "");
+ allNodes[7] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[7].style.setProperty("float", "left", "");
+ allNodes[30] = document.createElementNS("http://www.w3.org/1998/Math/MathML", "munder");
+ (allNodes[7] || allNodes[5] || document.body).appendChild(allNodes[30]);
+ (allNodes[5] || document.body).appendChild(allNodes[7]);
+ allNodes[17] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[17].style.setProperty("display", "inline-block", "");
+ (allNodes[5] || document.body).appendChild(allNodes[17]);
+ allNodes[20] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ (allNodes[5] || document.body).appendChild(allNodes[20]);
+ allNodes[23] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[23].style.setProperty("float", "left", "");
+ allNodes[25] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[25].style.setProperty("display", "inline-block", "");
+ (allNodes[23] || allNodes[5] || document.body).appendChild(allNodes[25]);
+ (allNodes[5] || document.body).appendChild(allNodes[23]);
+ (document.body).appendChild(allNodes[5]);
+ document.documentElement.offsetHeight;
+ allNodes[34] = document.createElementNS("http://www.w3.org/1998/Math/MathML", 'maligngroup');
+ allNodes[17].appendChild(allNodes[34]);
+ document.documentElement.offsetHeight;
+ allNodes[30].setAttribute('accentunder', "false");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/788360.html b/layout/base/crashtests/788360.html
new file mode 100644
index 000000000..b35bfdfe5
--- /dev/null
+++ b/layout/base/crashtests/788360.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<body onload="document.documentElement.offsetHeight; document.getElementById('v').style.counterReset='chicken'; document.documentElement.offsetHeight;">
+
+<div style="backface-visibility: hidden; perspective: 12em; display: table;"><div style="-moz-column-count: 2; white-space: pre;" id="v">x<span style="float: right; display: inline-block; width: 24px; height: 24px; background: yellow;"></span></div></div>
+
+</body>
diff --git a/layout/base/crashtests/793848.html b/layout/base/crashtests/793848.html
new file mode 100644
index 000000000..5d9bba272
--- /dev/null
+++ b/layout/base/crashtests/793848.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+ function f(event) {
+ document.removeEventListener("DOMAttrModified", f, false);
+
+ // dumpln(event.attrChange); /* 2 (MutationEvent.ADDITION) */
+ // dumpln(event.attrName); /* "curpos" */
+ // dumpln(event.newValue); /* "0" */
+
+ // (gdb) break nsGlobalWindow::Dump
+ dump("[[[[DOMAttrModified\n");
+ document.removeChild(svgUse);
+ dump("]]]]\n");
+ }
+
+ var svgUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
+ document.removeChild(document.documentElement);
+ document.addEventListener("DOMAttrModified", f, false);
+ document.appendChild(svgUse);
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/795646.html b/layout/base/crashtests/795646.html
new file mode 100644
index 000000000..5ef210f11
--- /dev/null
+++ b/layout/base/crashtests/795646.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="position: fixed; backface-visibility: hidden;">
+<body onload="setTimeout(function() { document.documentElement.style.MozBackfaceVisibility = 'hidden'; }, 0);">
+<div style="position: fixed; height: 8px; width: 200px; background-color: yellow;"></div>
+V
+
+</body></html>
diff --git a/layout/base/crashtests/802902.html b/layout/base/crashtests/802902.html
new file mode 100644
index 000000000..a7b6ada8d
--- /dev/null
+++ b/layout/base/crashtests/802902.html
@@ -0,0 +1,10 @@
+<div style=width:1;height:5000><script>
+document.onscroll=function(){alert("Scroll down as soon as you press ok!");}
+
+function initCF() {
+setTimeout("CFcrash()", 190);
+}
+document.addEventListener("DOMContentLoaded", initCF, false);
+function CFcrash() {
+try { window.scrollByLines(3); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/806056-1.html b/layout/base/crashtests/806056-1.html
new file mode 100644
index 000000000..7472bac74
--- /dev/null
+++ b/layout/base/crashtests/806056-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementsByTagName("td")[0].style.position = "absolute";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<table border=1><tbody><tr><td>X</td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/base/crashtests/806056-2.html b/layout/base/crashtests/806056-2.html
new file mode 100644
index 000000000..c0fd20fec
--- /dev/null
+++ b/layout/base/crashtests/806056-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementsByTagName("td")[0].style.position = "absolute";
+ document.body.getClientRects(); //flush
+ document.getElementsByTagName("tbody")[0].style.transformStyle = "preserve-3d";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<table><tbody><tr><td></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/base/crashtests/812665.html b/layout/base/crashtests/812665.html
new file mode 100644
index 000000000..1d2edf11b
--- /dev/null
+++ b/layout/base/crashtests/812665.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('x').style.transformStyle = '';">
+<div><span id="x" style="transform-style: preserve-3d;"><div><div style="position: fixed;"></div></div></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/813372-1.html b/layout/base/crashtests/813372-1.html
new file mode 100644
index 000000000..f2cf3a78d
--- /dev/null
+++ b/layout/base/crashtests/813372-1.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<!-- There is, at present, no official xsd for (X)HTML5. A pity. Usefulness would depend on the parser and extensions made by the site. -->
+<title>testcase</title>
+ <style type="text/css">
+* { margin: 0; padding: 0; }
+.hide { top: 80% !important; width: 75% !important; height: 50% !important; }
+
+#details
+{
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 0%;
+ border: 10mm dotted red;
+ border-radius: 100em;
+ background-color: lime;
+ height: 0%;
+ overflow: scroll;
+ -moz-transition-property: top width;
+ -moz-transition-duration: 0.75s;
+ opacity: 0.9;
+}
+
+ </style>
+</head>
+<body>
+
+
+<section id="details" class="hide">
+I'm a test of hiding animation
+<button onclick="this.parentNode.classList.add('hide')">Click me to hide</button>
+</section>
+
+<script>
+var kNumIterations = 5;
+var currentIteration = 0;
+var inrval;
+
+function doe() {
+ if (++currentIteration >= kNumIterations) {
+ clearInterval(inrval);
+ document.documentElement.removeAttribute('class');
+ } else {
+ document.getElementById('details').classList.toggle('hide');
+ }
+}
+document.addEventListener("MozReftestInvalidate", function(){ inrval = setInterval(doe, 1000); }, false);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/817219-iframe.html b/layout/base/crashtests/817219-iframe.html
new file mode 100644
index 000000000..0be322dac
--- /dev/null
+++ b/layout/base/crashtests/817219-iframe.html
@@ -0,0 +1,35 @@
+<html>
+<script>
+function start() {
+o3=document.createElement('input');
+tmp = o3.ownerDocument.createElement('iframe');
+document.body.appendChild(tmp);
+o4=tmp.contentDocument;
+cb_3=function() { var f = callback_3; callback_3 = null; return f(arguments); }
+o3.addEventListener('change', cb_3, false);
+o51=document.createElement('img');
+o94=document.createElement('input');
+o94.type='checkbox';
+o3.appendChild(o94);
+o192=document.createElement('input');
+o192.type='button';
+o94.appendChild(o192);
+o263=document.createEvent('MouseEvents');
+o263.initMouseEvent('click', true, true, window,0, 0, 0, 0, 0, false, false, false, false, 0, null);
+o192.dispatchEvent(o263)
+}
+function callback_3() {
+o192.addEventListener('DOMNodeRemoved', callback_21, true);
+o51.appendChild(o192);
+}
+function callback_21() {
+o4.documentElement.appendChild(o192);
+location.reload();
+}
+</script>
+<body>
+<script>
+window.setTimeout("start();", 10);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/817219.html b/layout/base/crashtests/817219.html
new file mode 100644
index 000000000..b474c229b
--- /dev/null
+++ b/layout/base/crashtests/817219.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 817219</title>
+<script>
+function reload() {
+ this.location.reload();
+}
+// Run the test for 2 seconds
+setTimeout(function() {
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+</script>
+</head>
+<body onload="document.body.getBoundingClientRect()">
+
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="817219-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="817219-iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/818454.html b/layout/base/crashtests/818454.html
new file mode 100644
index 000000000..4f3cd1038
--- /dev/null
+++ b/layout/base/crashtests/818454.html
@@ -0,0 +1,24 @@
+><pre>><style>#parent {
+ position: absolute;
+ }
+#parent::first-letter {
+</style>
+<video></video>>>><div id=parent>
+ <i> 9Z 1CU %b 1 *v
+` mMx#[j
+>></div>
+>><q><dt>><style>
+.class1 { stroke: none; direction: rtl;</style><script>
+var docElement = document.body;
+docElement.contentEditable = "true";
+function crash() {
+test1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "degree");
+docElement.appendChild(test1);
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
+docElement.appendChild(test2);
+test3 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mover");
+test3.setAttribute("class", "class1");
+docElement.appendChild(test3);
+}
+document.addEventListener("DOMContentLoaded", crash, false);
+</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/822865.html b/layout/base/crashtests/822865.html
new file mode 100644
index 000000000..86487bf96
--- /dev/null
+++ b/layout/base/crashtests/822865.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html style="-moz-appearance: radio; display: table;">
+<body onload="document.elementFromPoint(0, 0);"></body>
+</html>
diff --git a/layout/base/crashtests/824862.html b/layout/base/crashtests/824862.html
new file mode 100644
index 000000000..46aadde87
--- /dev/null
+++ b/layout/base/crashtests/824862.html
@@ -0,0 +1,5 @@
+<style>.error:before {
+ content: counter(c, none) "z";
+ display: flex;
+</style>
+><body style="overflow-x: -moz-hidden-unscrollable; ">><div class=error> \ No newline at end of file
diff --git a/layout/base/crashtests/826163.html b/layout/base/crashtests/826163.html
new file mode 100644
index 000000000..71f8562d3
--- /dev/null
+++ b/layout/base/crashtests/826163.html
@@ -0,0 +1,11 @@
+<cell id=test1>h A</cell>>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<card>>>>>
+zq
+^I~
+U5=m 9l( 5 n 3
+=o~
+i 0 U]C`EE# RH%o9)&amp;` |: Z {Q-4 `.b^,G /7
+<body dir=rtl>>><script>
+document.addEventListener("DOMContentLoaded", CFcrash, false);
+function CFcrash() {
+try { document.implementation.createDocument("", "", null).adoptNode(test1); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/830138-1.html b/layout/base/crashtests/830138-1.html
new file mode 100644
index 000000000..c61403f63
--- /dev/null
+++ b/layout/base/crashtests/830138-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<math><menclose id="m" style="transform:translate(10px,0)">
+ <ll style="display:block">
+ <ll id=test2 style="display:none; position: fixed"></ll>
+ </ll>
+</menclose></math>
+<script>
+function doTest() {
+ document.getElementById("test2").setAttribute("style", "position:fixed")
+ document.documentElement.removeAttribute("class");
+}
+window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/830192-1.html b/layout/base/crashtests/830192-1.html
new file mode 100644
index 000000000..2a9183a20
--- /dev/null
+++ b/layout/base/crashtests/830192-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+.test {
+ position:fixed;
+ display:none;
+ width:100px; height:100px;
+ background:yellow;
+}
+.doTest .test {
+ display:block;
+}
+</style>
+</head>
+<body>
+<table>
+<tr style="transform:translate(10px,0)">
+<td>
+ <div class="test"></div>
+</td>
+</tr>
+</table>
+<script>
+function doTest() {
+ document.documentElement.setAttribute("class", "doTest");
+}
+window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/830299-1.html b/layout/base/crashtests/830299-1.html
new file mode 100644
index 000000000..8b5741417
--- /dev/null
+++ b/layout/base/crashtests/830299-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+.test {
+ position:fixed;
+ display:none;
+ width:100px; height:100px;
+ background:yellow;
+}
+.doTest .test {
+ display:block;
+}
+</style>
+</head>
+<body>
+<div style="transform:translate(10px,0); overflow:scroll; width:200px; height:200px;">
+ <div class="test"></div>
+</div>
+<script>
+function doTest() {
+ document.documentElement.setAttribute("class", "doTest");
+}
+window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/833604-1.html b/layout/base/crashtests/833604-1.html
new file mode 100644
index 000000000..a97d88bf6
--- /dev/null
+++ b/layout/base/crashtests/833604-1.html
@@ -0,0 +1,18 @@
+<html>
+<script>
+function start() {
+try{o0=document.body;}catch(e){}
+try{o11=document.createElement('input');;}catch(e){}
+try{o0.appendChild(o11);}catch(e){}
+try{document.documentElement.offsetHeight;}catch(e){}
+try{o0.style.cssText = '-moz-transform: matrix(1, -0.2, 0, 1, 0, 0);'}catch(e){}
+try{o11.style.position='fixed';}catch(e){}
+window.setTimeout('window.start_waitfor0()',10);
+}
+function start_waitfor0() {
+try{o0.style.display='table-column';}catch(e){}
+try{o11.offsetHeight;}catch(e){}
+}
+</script>
+<body onload="start()"></body>
+</html>
diff --git a/layout/base/crashtests/835056.html b/layout/base/crashtests/835056.html
new file mode 100644
index 000000000..874b97a35
--- /dev/null
+++ b/layout/base/crashtests/835056.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+html, body
+{
+ overflow: hidden;
+}
+
+body
+{
+ backface-visibility: hidden;
+}
+</style>
+</head>
+<body>
+<div style="position: fixed"></div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/836990-1.html b/layout/base/crashtests/836990-1.html
new file mode 100644
index 000000000..d81331467
--- /dev/null
+++ b/layout/base/crashtests/836990-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="overflow: auto; transform:translate(100px,0);">
+ <div style="position: relative;"><div id="x" style="position:fixed; display:none"></div></div>
+</div>
+<script>
+document.body.getBoundingClientRect();
+document.getElementById('x').style.display = '';
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/840480.html b/layout/base/crashtests/840480.html
new file mode 100644
index 000000000..bd79c86f3
--- /dev/null
+++ b/layout/base/crashtests/840480.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<BODY>
+<CENTER ID="Test-CENTER">
+</CENTER>
+<BR />
+<B ID="Test-B" ></B>
+<BR />
+<DL>
+ <DT ID="Test-DT" CLASS="DT-class"></DT>
+</DL>
+<SPAN ID="Test-SPAN" CLASS="SPAN-class"></SPAN>
+<DFN ID="Test-DFN" >
+ <VAR ID="Test-VAR"></VAR>
+ <CITE ID="Test-CITE">Boom</CITE>
+</DFN>
+<ABBR ID="Test-ABBR" ></ABBR>
+<script type="text/javascript">
+
+ document.head.appendChild(document.createElement("style"));
+var styleSheet0 = document.styleSheets[0];
+
+var test0=document.getElementById("Test-DT")
+var test4=document.getElementById("Test-DFN")
+var test5=document.getElementById("Test-CENTER")
+var test7=document.getElementById("Test-B")
+var test18=document.getElementById("Test-ABBR")
+var test19=document.getElementById("Test-SPAN")
+
+for(x=0;x<14;x++){
+ test18.appendChild(test5.cloneNode(true));
+test18.appendChild(test7);
+test19.appendChild(test4.cloneNode(true));
+}
+
+styleSheet0.insertRule('.U-class,.DT-class,.SPAN-class,.I-class{display: table-caption; content: counter(c, binary); counter-increment:c;}',0);
+window.scrollTo(688,835)
+styleSheet0.insertRule('#Test-SPAN,#Test-NOFRAMES,#Test-CITE,#Test-EM{list-style-type:upper-roman; -moz-transition-property:none; -moz-transform:rotate(-90deg) translate(-2em, -18em); background-clip:border-box; border-collapse:collapsed; }',0);
+test7.style.setProperty('overflow','hidden','important');
+test7.appendChild(test0.cloneNode(true));
+</script>
+
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/847242.html b/layout/base/crashtests/847242.html
new file mode 100644
index 000000000..c148dbb66
--- /dev/null
+++ b/layout/base/crashtests/847242.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.f { unicode-bidi: bidi-override; width: 1px; white-space: pre-line; }
+.f:first-letter { font-size: 200% }
+</style>
+</head>
+<body>
+<div class="f">&#x2029;&#x062a;&#x8401;
+x</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/852293.html b/layout/base/crashtests/852293.html
new file mode 100644
index 000000000..d1098d080
--- /dev/null
+++ b/layout/base/crashtests/852293.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+"use strict";
+
+var i = 0;
+var x;
+var fixedDiv;
+var sheet;
+
+function start()
+{
+ clearChildren(document.documentElement);
+
+ for (var j = 0; j < 10; ++j) {
+ document.documentElement.appendChild(document.createElement("div"));
+ }
+ x = document.getElementsByTagName("div")[0];
+
+ fixedDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ fixedDiv.style.setProperty("position", "fixed", "");
+
+ setTimeout(makeChanges, 10);
+}
+
+function makeChanges()
+{
+ ++i;
+ x.appendChild(fixedDiv);
+ sheet = document.createElement("style");
+ sheet.appendChild(document.createTextNode("* { transform: matrix(1, 2, 3, 4, 5, 6); }"));
+ document.documentElement.appendChild(sheet);
+ if (i >= 200) {
+ document.documentElement.removeAttribute("class");
+ return;
+ }
+ setTimeout(revertChanges, 10);
+}
+
+function revertChanges()
+{
+ x.removeChild(fixedDiv);
+ document.documentElement.removeChild(sheet);
+ bounceDE();
+ setTimeout(makeChanges, 10);
+}
+
+
+function bounceDE()
+{
+ var de = document.documentElement;
+ document.removeChild(de);
+ document.appendChild(de);
+}
+
+function clearChildren(root)
+{
+ while(root.firstChild) { root.removeChild(root.firstChild); }
+}
+
+</script>
+</head>
+
+<body onload="start();"></body>
+</html>
diff --git a/layout/base/crashtests/859526-1.html b/layout/base/crashtests/859526-1.html
new file mode 100644
index 000000000..9e2574fd7
--- /dev/null
+++ b/layout/base/crashtests/859526-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="transform-style: preserve-3d;"><head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1252"></head><body>
+<iframe></iframe>
+
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/859630-1.html b/layout/base/crashtests/859630-1.html
new file mode 100644
index 000000000..ca0cd9df8
--- /dev/null
+++ b/layout/base/crashtests/859630-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="UTF-8">
+</head><body>
+<div style="display: table-caption"><iframe></iframe></div>
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/860579-1.html b/layout/base/crashtests/860579-1.html
new file mode 100644
index 000000000..3f7ef558b
--- /dev/null
+++ b/layout/base/crashtests/860579-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function addFrame(contents)
+{
+ var frame = document.createElement("iframe");
+ frame.src = "data:text/html," + contents;
+ document.body.appendChild(frame);
+}
+function boom()
+{
+ addFrame("1");
+ document.documentElement.offsetHeight;
+ addFrame("2");
+ document.body.style.display = "table-caption";
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/866588.html b/layout/base/crashtests/866588.html
new file mode 100644
index 000000000..4e9abfdd5
--- /dev/null
+++ b/layout/base/crashtests/866588.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+body { white-space: pre-wrap; width: 1ch; font-family: monospace }
+body:first-line { }
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.body.textContent = "\n\u202AX ";
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.createTextNode("Y"));
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/876092.html b/layout/base/crashtests/876092.html
new file mode 100644
index 000000000..d52e2d4f6
--- /dev/null
+++ b/layout/base/crashtests/876092.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <style>
+ #s2::before {
+ content: "a";
+ position: absolute;
+ }
+ #s1 {
+ overflow: -moz-hidden-unscrollable;
+ }
+ #s3 {
+ position: relative;
+ }
+ #s5 {
+ position: absolute;
+ }
+ </style>
+ </head>
+ <body>
+ <strike id="s1">
+ <strike id="s2">
+ <small id="s3">
+ <div>
+ <div id="s4"></div>
+ </strike>
+ </div>
+ </body>
+</html>
+
diff --git a/layout/base/crashtests/876221.html b/layout/base/crashtests/876221.html
new file mode 100644
index 000000000..3c1ae4f5a
--- /dev/null
+++ b/layout/base/crashtests/876221.html
@@ -0,0 +1,39 @@
+<html>
+<script>
+function start() {
+o0=tmp = document.createElement('iframe');
+document.getElementById('store_div').appendChild(tmp);
+o19=document.documentElement;
+tmp.id = 'id28'
+o119=tmp = document.createElement('iframe');
+tmp.id = 'id63'
+o19.appendChild(tmp)
+o152=document.getElementById('id63').contentDocument;
+o515=o152.createElement('xml');
+o547=document.createElementNS('http://www.w3.org/1999/xhtml','feFuncB');
+o552=document.createElementNS('http://www.w3.org/1999/xhtml','munder');
+o569=window.document.getElementById('id28').contentWindow.document;
+document.body.appendChild(o552);
+o552.appendChild(o547);
+o547.appendChild(o515);
+o582=o569.createElement('dl');
+o588=document.createElement('input');
+o552.style.cssText = 'overflow: -moz-hidden-unscrollable; '
+o552.style.position='absolute';
+o600=o515.offsetParent;
+o619=document.createElement('input');
+o635=o569.createElement('input');
+o635.type='image';
+o600.appendChild(o635);
+o588.style.position='absolute';
+o635.appendChild(o582);
+o588.appendChild(o619);
+o670=o619.parentNode;
+o552.style.position=null;
+o582.appendChild(o670);
+o635.style.position='relative';
+}
+</script>
+<body onload="start()"><div id="store_div"></div></body>
+</html>
+
diff --git a/layout/base/crashtests/89101-1.html b/layout/base/crashtests/89101-1.html
new file mode 100644
index 000000000..09ce18524
--- /dev/null
+++ b/layout/base/crashtests/89101-1.html
@@ -0,0 +1,22 @@
+<HTML>
+<FORM>
+
+<fieldset STYLE="
+
+ position:fixed;
+ left:
+ 311;
+ top:
+ 248;
+ width:
+ 371;
+
+ height:
+ 184;
+
+
+ ">
+<input TYPE="text" NAME="Sub1104001010" VALUE="" TABINDEX="11" MAXLENGTH="10">
+</FIELDSET>
+</FORM>
+
diff --git a/layout/base/crashtests/89358-1.html b/layout/base/crashtests/89358-1.html
new file mode 100644
index 000000000..39702f7be
--- /dev/null
+++ b/layout/base/crashtests/89358-1.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html , charset=x-user-defined">
+</HEAD>
+<BODY>
+<PRE>
+<A HREF="http://www.test.net/">http://www.test.net </A> Mozilla-0.9.2 is dying - blah.!?
+</PRE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/897852.html b/layout/base/crashtests/897852.html
new file mode 100644
index 000000000..a7fe1437b
--- /dev/null
+++ b/layout/base/crashtests/897852.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: table-column;">
+<iframe src="data:text/html,<html contenteditable=''><script>var f = window.frameElement; window.addEventListener('load', function() { window.addEventListener('DOMNodeInserted', function() { f.parentNode.removeChild(f); }, true); f.parentNode.style.cssFloat = 'right'; }, false);</script>";"
+</body>
+</html>
diff --git a/layout/base/crashtests/898913.html b/layout/base/crashtests/898913.html
new file mode 100644
index 000000000..39adbd463
--- /dev/null
+++ b/layout/base/crashtests/898913.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+div { page-break-inside: avoid; }
+div:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ var d = document.getElementById('d');
+ d.removeChild(d.firstChild);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="d">&#x202B;</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/90205-1.html b/layout/base/crashtests/90205-1.html
new file mode 100644
index 000000000..0092c5680
--- /dev/null
+++ b/layout/base/crashtests/90205-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <title>Bug 90205</title>
+</head>
+<body>
+ <span style="font-family: serif;">
+ <span style="float: left;"></span>
+ </span>
+ <font size=2>
+ <meta>
+ <form></form>
+ </font>
+ <body topmargin="0">
+</body>
+</html>
diff --git a/layout/base/crashtests/919434.html b/layout/base/crashtests/919434.html
new file mode 100644
index 000000000..6de782b51
--- /dev/null
+++ b/layout/base/crashtests/919434.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html style="overflow: scroll;">
+<meta charset="UTF-8">
+<body style="overflow: hidden; position: fixed;"><input><div style="position: sticky;">C</div></body>
+</html>
diff --git a/layout/base/crashtests/926728.html b/layout/base/crashtests/926728.html
new file mode 100644
index 000000000..85883f0fe
--- /dev/null
+++ b/layout/base/crashtests/926728.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <span id="x" style="position: sticky; bottom: 75px;">
+ <div></div>
+ </span>
+ <script>
+ document.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById('x').style.bottom = '-3000px';
+ });
+ </script>
+ </body>
+</html>
diff --git a/layout/base/crashtests/930381.html b/layout/base/crashtests/930381.html
new file mode 100644
index 000000000..eb17c9d5e
--- /dev/null
+++ b/layout/base/crashtests/930381.html
@@ -0,0 +1,122 @@
+<script>
+function fuzz(){
+ var a=document.getElementById('a');
+ var b=document.getElementById('b');
+ var pa=a.parentNode;
+ b.parentNode.replaceChild(a,b);
+ pa.appendChild(b);
+}
+</script>
+<big>
+<menu>
+<address>
+<optgroup label="a"></optgroup>
+"
+<blockquote>
+a
+<ruby>a</ruby>
+</address>
+<s dir="rtl">
+<section>
+<fieldset id="a"><iframe></iframe></fieldset>
+</section>
+<body onmouseover="fuzz()">
+<video id="b">
+
+<!--
+==21242==ERROR: AddressSanitizer: heap-use-after-free on address 0x61700022a21c at pc 0x7f0fe52bd9bc bp 0x7fff20ff6650 sp 0x7fff20ff6648
+READ of size 4 at 0x61700022a21c thread T0
+ #0 0x7f0fe52bd9bb (libxul.so!PresShell::DispatchSynthMouseMove(mozilla::WidgetGUIEvent*, bool)+0x1db)
+ Line 75 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/RestyleManager.h"
+ #1 0x7f0fe52cc0c4 (libxul.so!PresShell::ProcessSynthMouseMoveEvent(bool)+0xde4)
+ Line 5256 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsPresShell.cpp"
+ #2 0x7f0fe52f0547 (libxul.so!nsRefreshDriver::Tick(long, mozilla::TimeStamp)+0xbb7)
+ Line 1074 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsRefreshDriver.cpp"
+ #3 0x7f0fe52f64e0 (libxul.so!mozilla::RefreshDriverTimer::Tick()+0x1f0)
+ Line 168 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsRefreshDriver.cpp"
+ #4 0x7f0fe8de4c31 (libxul.so!nsTimerImpl::Fire()+0x6d1)
+ Line 546 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsTimerImpl.cpp"
+ #5 0x7f0fe8de52d6 (libxul.so!nsTimerEvent::Run()+0x66)
+ Line 630 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsTimerImpl.cpp"
+ #6 0x7f0fe8ddc019 (libxul.so!nsThread::ProcessNextEvent(bool, bool*)+0xaa9)
+ Line 622 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsThread.cpp"
+ #7 0x7f0fe8d08371 (libxul.so!NS_ProcessNextEvent(nsIThread*, bool)+0xb1)
+ Line 251 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/glue/nsThreadUtils.cpp"
+ #8 0x7f0fe7955091 (libxul.so!mozilla::ipc::MessagePump::Run(base::MessagePump::Delegate*)+0x311)
+ Line 85 of "/builds/slave/m-in-l64-asan-0000000000000000/build/ipc/glue/MessagePump.cpp"
+ #9 0x7f0fe8ef7653 (libxul.so!MessageLoop::Run()+0x1c3)
+ Line 220 of "/builds/slave/m-in-l64-asan-0000000000000000/build/ipc/chromium/src/base/message_loop.cc"
+ #10 0x7f0fe7733cac (libxul.so!nsBaseAppShell::Run()+0x5c)
+ Line 161 of "/builds/slave/m-in-l64-asan-0000000000000000/build/widget/xpwidgets/nsBaseAppShell.cpp"
+ #11 0x7f0fe7135d9e (libxul.so!nsAppStartup::Run()+0xbe)
+ Line 268 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/components/startup/nsAppStartup.cpp"
+ #12 0x7f0fe46bf1c5 (libxul.so!XREMain::XRE_mainRun()+0x1e05)
+ Line 3886 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #13 0x7f0fe46c00fa (libxul.so!XREMain::XRE_main(int, char**, nsXREAppData const*)+0x4fa)
+ Line 3954 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #14 0x7f0fe46c102b (libxul.so!XRE_main+0x3ab)
+ Line 4156 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #15 0x459d1d (firefox!main+0x94d)
+ Line 275 of "/builds/slave/m-in-l64-asan-0000000000000000/build/browser/app/nsBrowserApp.cpp"
+ #16 0x7f0ff3d5876c (libc.so.6!__libc_start_main+0xec)
+ Line 226 of "libc-start.c"
+ #17 0x45929c (firefox!_start+0x28)
+0x61700022a21c is located 28 bytes inside of 760-byte region [0x61700022a200,0x61700022a4f8)
+freed by thread T0 here:
+ #0 0x4461a5 (firefox!free+0x55)
+ Line 64 of "/builds/slave/moz-toolchain/src/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc"
+ #1 0x7f0fe529f118 (libxul.so!mozilla::RestyleManager::Release()+0x138)
+ Line 225 of "../../dist/include/mozilla/mozalloc.h"
+previously allocated by thread T0 here:
+ #0 0x4462e5 (firefox!malloc+0x55)
+ Line 74 of "/builds/slave/moz-toolchain/src/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc"
+ #1 0x7f0feddfe5c8 (libmozalloc.so!moz_xmalloc+0x8)
+ Line 54 of "/builds/slave/m-in-l64-asan-0000000000000000/build/memory/mozalloc/mozalloc.cpp"
+ #2 0x7f0fe5230421 (libxul.so!nsDocumentViewer::InitInternal(nsIWidget*, nsISupports*, nsIntRect const&, bool, bool, bool)+0x581)
+ Line 824 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsDocumentViewer.cpp"
+ #3 0x7f0fe522fe90 (libxul.so!nsDocumentViewer::Init(nsIWidget*, nsIntRect const&)+0x20)
+ Line 642 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsDocumentViewer.cpp"
+ #4 0x7f0fe929f537 (libxul.so!nsDocShell::Embed(nsIContentViewer*, char const*, nsISupports*)+0xe7)
+ Line 6397 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDocShell.cpp"
+ #5 0x7f0fe92b14f4 (libxul.so!nsDocShell::CreateContentViewer(char const*, nsIRequest*, nsIStreamListener**)+0x1084)
+ Line 8173 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDocShell.cpp"
+ #6 0x7f0fe9254ad4 (libxul.so!nsDSURIContentListener::DoContent(char const*, bool, nsIRequest*, nsIStreamListener**, bool*)+0x304)
+ Line 122 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDSURIContentListener.cpp"
+ #7 0x7f0fe92f698f (libxul.so!nsDocumentOpenInfo::TryContentListener(nsIURIContentListener*, nsIChannel*)+0x6ef)
+ Line 680 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #8 0x7f0fe92f433c (libxul.so!nsDocumentOpenInfo::DispatchContent(nsIRequest*, nsISupports*)+0x67c)
+ Line 382 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #9 0x7f0fe92f3aaf (libxul.so!nsDocumentOpenInfo::OnStartRequest(nsIRequest*, nsISupports*)+0x32f)
+ Line 258 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #10 0x7f0fe4964bc2 (libxul.so!nsBaseChannel::OnStartRequest(nsIRequest*, nsISupports*)+0x1e2)
+ Line 718 of "/builds/slave/m-in-l64-asan-0000000000000000/build/netwerk/base/src/nsBaseChannel.cpp"
+Shadow bytes around the buggy address:
+ 0x0c2e8003d3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d420: 00 fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
+ 0x0c2e8003d430: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
+=>0x0c2e8003d440: fd fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d450: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d460: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d470: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d480: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d490: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
+Shadow byte legend (one shadow byte represents 8 application bytes):
+ Addressable: 00
+ Partially addressable: 01 02 03 04 05 06 07
+ Heap left redzone: fa
+ Heap right redzone: fb
+ Freed heap region: fd
+ Stack left redzone: f1
+ Stack mid redzone: f2
+ Stack right redzone: f3
+ Stack partial redzone: f4
+ Stack after return: f5
+ Stack use after scope: f8
+ Global redzone: f9
+ Global init order: f6
+ Poisoned by user: f7
+ ASan internal: fe
+==21242==ABORTING
+-->
diff --git a/layout/base/crashtests/931450.html b/layout/base/crashtests/931450.html
new file mode 100644
index 000000000..fa8dfd59e
--- /dev/null
+++ b/layout/base/crashtests/931450.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html dir="rtl">
+<head>
+<meta charset="UTF-8">
+<body>
+
+<div style="position: fixed;"><p style="overflow-y: hidden;">A<span style="position: sticky;">B$</span></p></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/931460-1.html b/layout/base/crashtests/931460-1.html
new file mode 100644
index 000000000..812cd9b38
--- /dev/null
+++ b/layout/base/crashtests/931460-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<body><fieldset style="overflow: hidden;"><legend style="position: sticky;"></legend></fieldset></body>
+</html>
diff --git a/layout/base/crashtests/931464.html b/layout/base/crashtests/931464.html
new file mode 100644
index 000000000..637020331
--- /dev/null
+++ b/layout/base/crashtests/931464.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var fieldset = document.getElementById("f");
+ for (var i = 0; i < 2; ++i)
+ fieldset.appendChild(document.createElement("span"));
+}
+
+</script>
+<body onload="boom();">
+<fieldset id="f" style="overflow: auto;"></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/935765-1.html b/layout/base/crashtests/935765-1.html
new file mode 100644
index 000000000..c30f492fb
--- /dev/null
+++ b/layout/base/crashtests/935765-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="document.getElementById('a').remove();">
+<fieldset style="overflow: scroll;"><legend><textarea id="a" style="position: sticky;"></textarea></legend></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/936988-1.html b/layout/base/crashtests/936988-1.html
new file mode 100644
index 000000000..062125e35
--- /dev/null
+++ b/layout/base/crashtests/936988-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="document.getElementById('f').appendChild(document.createTextNode('X'));">
+<fieldset id="f"><legend style="display: table-row-group;"></legend></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/942690.html b/layout/base/crashtests/942690.html
new file mode 100644
index 000000000..da64dd00d
--- /dev/null
+++ b/layout/base/crashtests/942690.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>14 Rendering — HTML Standard</title>
+ <style>
+ pre.css:first-line { color: #AAAA50; }
+ </style>
+ </head>
+<body>
+ <pre class="css">foo
+
+bar ׳
+</pre>
+</body></html>
diff --git a/layout/base/crashtests/973390-1.html b/layout/base/crashtests/973390-1.html
new file mode 100644
index 000000000..89e6c2694
--- /dev/null
+++ b/layout/base/crashtests/973390-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="display: flex;">
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: table-cell;"></body>
+</html>
diff --git a/layout/base/crashtests/99776-1.html b/layout/base/crashtests/99776-1.html
new file mode 100644
index 000000000..0ce2fcb76
--- /dev/null
+++ b/layout/base/crashtests/99776-1.html
@@ -0,0 +1,9 @@
+
+
+
+<html><head><title>Testcase for bug 99776</title></head>
+<body>
+
+<applet style="position:absolute;left:7;top:73;"></applet>
+
+</body></html>
diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list
new file mode 100644
index 000000000..e2cb99e04
--- /dev/null
+++ b/layout/base/crashtests/crashtests.list
@@ -0,0 +1,485 @@
+load 46043-1.html
+load 47843-1.html
+load 49122-1.html
+load 50257-1.html
+load 50395-1.html
+load 56746-1.html
+load 89101-1.html
+load 89358-1.html
+load 90205-1.html
+skip-if(cocoaWidget&&browserIsRemote) load 99776-1.html # Bug 849747
+load 118931-1.html
+load 121533-1.html
+load 123049-1.html
+load 123946-1.html
+load 128855-1.html
+load 133410-1.html
+load 143862-1a.html
+load 143862-1b.html
+load 143862-1c.html
+load 143862-2.html
+load 147320-1.html
+load 148245-1.html
+load 149014-1.html
+load 150431-1.html
+load 176915-1.html
+load 191272-1.html
+load 199696-1.html
+load 217903-1.html
+load 223064-1.html
+load 234851-1.html
+load 234851-2.html
+load 241300-1.html
+load 243159-1.html
+load 243159-2.xhtml
+load 243519-1.html
+load 244490-1.html
+load 254367-1.html
+load 263359-1.html
+load 265027-1.html
+load 265736-1.html
+load 265736-2.html
+load 265899-1.html
+load 265973-1.html
+asserts(6-12) load 265986-1.html # Bug 512405
+load 265999-1.html
+load 266222-1.html
+asserts(1-7) load 266360-1.html # bug 576358
+load 266445-1.html
+asserts(2) load 266445-2.html
+load 268157-1.html
+load 269566-1.html
+load 272647-1.html
+load 275746-1.html
+load 276053-1.html
+load 280708-1.html
+load 280708-2.html
+load 281333-1.html
+load 285212-1.html
+load 286813-1.html
+load 288790-1.html
+load 306940-1.html
+load 310267-1.xml
+load 310638-1.svg
+load 310638-2.html
+load 311661-1.xul
+load 311661-2.xul
+load 313086-1.xml
+load 317285-1.html
+load 317934-1.html
+load 320459-1.html
+load 321058-1.xul
+load 321058-2.xul
+load 321077-1.xul
+load 321077-2.xul
+load 322436-1.html
+load 325967-1.html
+load 325984-1.xhtml
+load 325984-2.html
+load 328944-1.xul
+load 329900-1.html
+load 330015-1.html
+load 331204-1.html
+load 331679-1.xhtml
+load 331679-2.xml
+load 331679-3.xml
+load 331883-1.html
+load 335140-1.html
+load 336291-1.html
+load 336999-1.xul
+load 337066-1.xhtml
+load 337268-1.html
+load 337419-1.html
+load 337476-1.xul
+load 338703-1.html
+load 339651-1.html
+load 340093-1.xul
+load 341382-1.html
+load 341382-2.html
+load 341858-1.html
+load 342145-1.xhtml
+load 343293-1.xhtml
+load 343293-2.xhtml
+load 343540-1.html
+load 344057-1.xhtml
+load 344064-1.html
+load 344300-1.html
+load 344300-2.html
+load 344340-1.xul
+load 347898-1.html
+load 348126-1.html
+load 348688-1.html
+load 348708-1.xhtml
+asserts(2) load 348729-1.html # bug 548836
+load 349095-1.xhtml
+load 350128-1.xhtml
+load 350267-1.html
+load 354133-1.html
+load 354766-1.xhtml
+load 354771-1.xul
+load 355989-1.xhtml
+load 355993-1.xhtml
+load 356325-1.xul
+load 358729-1.xhtml
+load 360339-1.xul
+load 360339-2.xul
+load 363729-1.html
+load 363729-2.html
+load 363729-3.html
+load 364427-1.html
+load 365909-1.xhtml
+load 365909-2.xhtml
+load 366128-1.xhtml
+load 366271-1.html
+load 366967-1.html
+load 367015-1.html
+load 367243-1.html
+load 367498-1.html
+load 367498-2.html
+load 369176-1.html
+load 369547-1.html
+load 369547-2.html
+load 369945-1.xhtml
+load 371681-1.xhtml
+load 372237-1.html
+load 372475-1.xhtml
+load 372550-1.html
+load 372576.xul
+load 373628-1.html
+load 373919.xhtml
+load 374193-1.xhtml
+load 374297-1.html
+load 374297-2.html
+load 376223-1.xhtml
+load 378325-1.html
+load 378682.html
+load 379105-1.xhtml
+load 379419-1.xhtml
+load 379768-1.html
+load 379799-1.html
+load 379920-1.svg
+load 379920-2.svg
+load 379975.html
+load 380096-1.html
+load 382204-1.html
+load 383102-1.xhtml
+load 383129-1.html
+load 383806-1.xhtml
+load 384344-1.html
+load 384392-1.xhtml
+load 384392-2.svg
+load 384649-1.xhtml
+load 385354.html
+load 385866-1.xhtml
+load 385880-1.xhtml
+load 386266-1.html
+load 386476.html
+load 387195-1.html
+load 387195-2.xhtml
+load 388715-1.html
+load 390976-1.html
+load 393326-1.html
+load 393326-2.html
+load 393661-1.html
+load 393801-1.html
+load 394014-1.html
+load 394014-2.html
+load 394150-1.xhtml
+load 397011-1.xhtml
+load 398510-1.xhtml
+load 398733-1.html
+load 398733-2.html
+load 399132-1.xhtml
+load 399219-1.xhtml
+load 399365-1.html
+load 399676-1.xhtml
+load 399687-1.html
+load 399940-1.xhtml
+load 399946-1.xhtml
+load 399951-1.html
+load 399994-1.html
+load 400185-1.xul
+load 400445-1.xhtml
+load 400904-1.xhtml
+load 401589-1.xul
+load 401734-1.html
+load 401734-2.html
+needs-focus pref(accessibility.browsewithcaret,true) load 403048.html
+skip load 403175-1.html # times out occasionally, bug 473680
+load 403245-1.html
+load 403454.html
+load 403569-1.xhtml
+load 403569-2.xhtml
+load 403569-3.xhtml
+load 404218-1.xhtml
+load 404491-1.html
+load 404721-1.xhtml
+load 404721-2.xhtml
+load 405049-1.xul
+load 405184-1.xhtml
+load 405186-1.xhtml
+load 406675-1.html
+load 408292.html
+load 408299.html
+load 408450-1.xhtml
+load 409461-1.xhtml
+load 409513.html
+load 410967.html
+load 411870-1.html
+load 412651-1.html
+load 413587-1.svg
+load 414058-1.html
+load 414175-1.xul
+load 415503.xhtml
+load 416107.xhtml
+HTTP load 419985.html
+load 420031-1.html
+load 420213-1.html
+load 420219-1.html
+load 420651-1.xhtml
+load 421203-1.xul
+load 421432.html
+load 422276.html
+asserts(0-1) load 423107-1.xhtml # bug 866955
+load 425981-1.html
+load 428113.xhtml
+load 428138-1.html
+load 428448-1.html
+load 429088-1.html
+load 429088-2.html
+load 429780-1.xhtml
+load 429865-1.html
+load 429881.html
+load 430569-1.html
+load 430569-2.html
+load 432752-1.svg
+load 433450-1.html
+load 436982-1.html
+load 437142-1.html
+load 439258-1.html
+load 439343.html
+load 444863-1.html
+load 444925-1.xul
+load 444967-1.html
+load 446328.html
+load 448488-1.html
+load 448543-1.html
+load 448543-2.html
+load 448543-3.html
+load 450319-1.xhtml
+asserts(1) load 453894-1.xhtml # Bug 398043
+load 454751-1.xul
+load 455063-1.html
+load 455063-2.html
+load 455063-3.html
+load 455171-4.html
+load 455623-1.html
+load 457362-1.xhtml
+load 457514.html
+asserts(0-1) load 460389-1.html # Bug 780985
+load 462392.html
+load 466763-1.html
+load 467881-1.html
+load 468491-1.html
+load 468546-1.xhtml
+load 468555-1.xhtml
+load 468563-1.html
+load 468578-1.xhtml
+# These three didn't actually crash without the resizing that the
+# browser does when setting up print preview, but adding them anyway.
+load 468645-1.xhtml
+load 468645-2.xhtml
+load 468645-3.xhtml
+load 469861-1.xhtml
+load 469861-2.xhtml
+load 470851-1.xhtml
+load 471594-1.xhtml
+asserts-if(Android&&!asyncPan,1-2) load 473042.xhtml # bug 1034369 (may also cause a few assertions to be registered on the next test)
+asserts(0-5) load 474075.html # bug 847368
+load 477333-1.xhtml
+load 477731-1.html
+load 479114-1.html
+load 479360-1.xhtml
+load 480686-1.html
+load 481806-1.html
+load 483604-1.xhtml
+load 485501-1.html
+load 487544-1.html
+load 488390-1.xhtml
+load 489691.html
+load 490376-1.xhtml
+load 490559-1.html
+load 490747.html
+load 491547-1.xul
+load 491547-2.xul
+load 492014.xhtml
+load 492112-1.xhtml
+load 492163-1.xhtml
+load 495350-1.html
+load 496011-1.xhtml
+load 497519-1.xhtml
+load 497519-2.xhtml
+load 497519-3.xhtml
+load 497519-4.xhtml
+load 499741-1.xhtml
+load 499841-1.xhtml
+load 499858-1.xhtml
+load 500467-1.html
+load 501878-1.html
+load 503936-1.html
+load 507119.html
+load 514104-1.xul
+load 522374-1.html
+load 522374-2.html
+load 526378-1.xul
+load 534367-1.xhtml
+load 534368-1.xhtml
+load 534768-1.html
+load 534768-2.html
+load 535721-1.xhtml
+load 535911-1.xhtml
+load 536623-1.xhtml
+load 536720.xul
+load 537059-1.xhtml
+load 537141-1.xhtml
+load 537562-1.xhtml
+load 537624-1.html
+load 537631-1.html
+load 538082-1.xul
+load 538207-1.xhtml
+load 538210-1.html
+load 538267-1.html
+load 540760.xul
+load 540771-1.xhtml
+load 541869-1.xhtml
+load 541869-2.html
+load 543648-1.html
+load 559705.xhtml
+load 560441-1.xhtml
+load 560447-1.html
+load 564063-1.html
+load 567292-1.xhtml
+load 569018-1.html
+load 570038-1.html
+load 572003.xul
+load 572582-1.xhtml
+load 576649-1.html
+load 579655.html
+load 580129-1.html
+load 580494-1.html
+load 580834-1.xhtml
+load 589787.html
+load 591075-1.html
+load 591998-1.html
+load 595039-1.html
+load 597924-1.html
+load 606432-1.html
+load 609821-1.xhtml
+load 613817-1.svg
+load 615146-1.html
+load 615781-1.xhtml
+load 616495-single-side-composite-color-border.html
+load 629035-1.html
+load 629908-1.html
+load 635329.html
+load 636229-1.html
+== 640272.html 640272-ref.html
+load 645193.html
+load 645572-1.html
+load 650475.xhtml
+load 650489.xhtml
+load 651342-1.html
+load 653133-1.html
+load 663295.html
+load 663662-1.html
+load 663662-2.html
+load 665837.html
+load 668579.html
+load 668941.xhtml
+load 670226.html
+asserts(2) load 675246-1.xhtml # Bug 675713
+load 690247-1.html
+load 690619-1.html
+load 691118-1.html
+load 695861.html
+load 695964-1.svg
+load 698335.html
+needs-focus pref(accessibility.browsewithcaret,true) load 699353-1.html
+load 701504.html
+load 707098.html
+load 709536-1.xhtml
+load 722137.html
+load 725535.html
+load 727601.html
+skip-if(Android) asserts(0-2) pref(dom.disable_open_during_load,false) load 735943.html # the assertion is bug 735966, for android bug 760271
+asserts(0-2) load 736389-1.xhtml # sometimes the above assertions are delayed and is reported on this test instead
+load 736924-1.html
+load 749816-1.html
+load 763223-1.html
+test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.lineThreshold,100) load 763702.xhtml
+load 767593-1.html
+load 767593-2.html
+load 770381-1.html
+load 772306.html
+load 788360.html
+load 793848.html
+load 795646.html
+skip load 802902.html # bug 901752
+load 806056-1.html
+load 806056-2.html
+load 812665.html
+load 813372-1.html
+load 817219.html
+load 818454.html
+load 822865.html
+load 824862.html
+load 826163.html
+load 833604-1.html
+load 835056.html
+load 836990-1.html
+load 840480.html
+load 847242.html
+pref(layers.progressive-paint,false) pref(layers.low-precision-buffer,false) load 852293.html
+pref(layers.force-active,true) load 859526-1.html
+pref(layers.force-active,true) load 859630-1.html
+load 860579-1.html
+load 866588.html
+load 876092.html
+load 876221.html
+load 897852.html
+asserts(4-6) asserts-if(Android&&!asyncPan,2) load 898913.html # bug 847368
+pref(layers.acceleration.disabled,true) pref(layers.force-active,true) load 919434.html
+load 926728.html
+load 930381.html
+load 931450.html
+load 931460-1.html
+load 931464.html
+load 935765-1.html
+load 936988-1.html
+load 942690.html
+load 973390-1.html
+load 1001237.html
+load 1009036.html
+load 1043163-1.html
+load 1061028.html
+load 1107508-1.html
+load 1116104.html
+load 1127198-1.html
+load 1140198.html
+load 1143535.html
+pref(layout.css.grid.enabled,true) load 1156588.html
+load 1162813.xul
+load 1163583.html
+load 1234622-1.html
+load 1235467-1.html
+pref(dom.webcomponents.enabled,true) load 1261351.html
+load 1270797-1.html
+load 1278455-1.html
+load 1286889.html
+load 1297835.html
+load 1288608.html
+load 1299736-1.html
+load 1308793.svg
+load 1308848-1.html
+load 1308848-2.html
+asserts(0-1) load 1343606.html # bug 1343948
diff --git a/layout/base/doc/AccessibleCaretEventHubStates.dot b/layout/base/doc/AccessibleCaretEventHubStates.dot
new file mode 100644
index 000000000..11a5af15c
--- /dev/null
+++ b/layout/base/doc/AccessibleCaretEventHubStates.dot
@@ -0,0 +1,42 @@
+// Steps to generate AccessibleCaretEventHubStates.png
+// 1. Install Graphviz
+// 2. dot -T png -o AccessibleCaretEventHubStates.png AccessibleCaretEventHubStates.dot
+//
+// Note: If the edge has 'constraint=false', it is not used in ranking the
+// nodes. http://www.graphviz.org/doc/info/attrs.html#d:constraint
+
+digraph event_hub_states {
+ node [style=filled];
+ edge [color="gray30", fontcolor="gray20", fontsize=12]
+
+ NoAction [label="NoAction\n(Initial)"color="#96FF2F"];
+ NoAction -> PressCaret [label="Press & on a caret"];
+ NoAction -> PressNoCaret [label="Press & not on a caret"];
+ NoAction -> Scroll [label="Scroll start"];
+
+ PressCaret [color="#84D8FF"];
+ PressCaret -> DragCaret [label="Move & distance is large"];
+ PressCaret -> NoAction [label="Release (synthesizing a tap)"];
+
+ DragCaret [color="#84D8FF"];
+ DragCaret -> DragCaret [label="Move"];
+ DragCaret -> NoAction [label="Release"];
+
+ PressNoCaret [color="#E8C516"];
+ PressNoCaret -> NoAction [label="Move & distance is large or\nRelease or\nBlur"];
+ PressNoCaret -> LongTap [label="Long tap"];
+ PressNoCaret -> Scroll [label="Scroll start", constraint=false];
+
+ LongTap [color="#E8C516"]
+ LongTap -> NoAction [label="Release"];
+ LongTap -> Scroll [label="Scroll start", constraint=false];
+
+ Scroll [color="#FF9022"]
+ Scroll -> PostScroll [label="Scroll end"];
+ Scroll -> NoAction [label="Blur"];
+
+ PostScroll [color="#FF9022"]
+ PostScroll -> Scroll [label="Scroll start"];
+ PostScroll -> NoAction [label="Blur or\nWait 300ms"];
+ PostScroll -> NoAction [label="Press (forward to NoAction)", constraint=false];
+}
diff --git a/layout/base/doc/AccessibleCaretEventHubStates.png b/layout/base/doc/AccessibleCaretEventHubStates.png
new file mode 100644
index 000000000..d4dfefc2f
--- /dev/null
+++ b/layout/base/doc/AccessibleCaretEventHubStates.png
Binary files differ
diff --git a/layout/base/gtest/TestAccessibleCaretEventHub.cpp b/layout/base/gtest/TestAccessibleCaretEventHub.cpp
new file mode 100644
index 000000000..5216a52dc
--- /dev/null
+++ b/layout/base/gtest/TestAccessibleCaretEventHub.cpp
@@ -0,0 +1,827 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <iostream>
+#include <string>
+
+#include "AccessibleCaretEventHub.h"
+#include "AccessibleCaretManager.h"
+#include "gfxPrefs.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+
+using ::testing::AtLeast;
+using ::testing::DefaultValue;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+using ::testing::_;
+
+// -----------------------------------------------------------------------------
+// This file test the state transitions of AccessibleCaretEventHub under
+// various combination of events and callbacks.
+
+namespace mozilla
+{
+
+class MockAccessibleCaretManager : public AccessibleCaretManager
+{
+public:
+ MockAccessibleCaretManager()
+ : AccessibleCaretManager(nullptr)
+ {
+ }
+
+ MOCK_METHOD2(PressCaret,
+ nsresult(const nsPoint& aPoint, EventClassID aEventClass));
+ MOCK_METHOD1(DragCaret, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD0(ReleaseCaret, nsresult());
+ MOCK_METHOD1(TapCaret, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD1(SelectWordOrShortcut, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD0(OnScrollStart, void());
+ MOCK_METHOD0(OnScrollEnd, void());
+ MOCK_METHOD0(OnScrollPositionChanged, void());
+ MOCK_METHOD0(OnBlur, void());
+};
+
+class MockAccessibleCaretEventHub : public AccessibleCaretEventHub
+{
+public:
+ using AccessibleCaretEventHub::NoActionState;
+ using AccessibleCaretEventHub::PressCaretState;
+ using AccessibleCaretEventHub::DragCaretState;
+ using AccessibleCaretEventHub::PressNoCaretState;
+ using AccessibleCaretEventHub::ScrollState;
+ using AccessibleCaretEventHub::PostScrollState;
+ using AccessibleCaretEventHub::LongTapState;
+ using AccessibleCaretEventHub::FireScrollEnd;
+
+ MockAccessibleCaretEventHub()
+ : AccessibleCaretEventHub(nullptr)
+ {
+ mManager = MakeUnique<MockAccessibleCaretManager>();
+ mInitialized = true;
+ }
+
+ virtual nsPoint GetTouchEventPosition(WidgetTouchEvent* aEvent,
+ int32_t aIdentifier) const override
+ {
+ // Return the device point directly.
+ LayoutDeviceIntPoint touchIntPoint = aEvent->mTouches[0]->mRefPoint;
+ return nsPoint(touchIntPoint.x, touchIntPoint.y);
+ }
+
+ virtual nsPoint GetMouseEventPosition(WidgetMouseEvent* aEvent) const override
+ {
+ // Return the device point directly.
+ LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->mRefPoint;
+ return nsPoint(mouseIntPoint.x, mouseIntPoint.y);
+ }
+
+ MockAccessibleCaretManager* GetMockAccessibleCaretManager()
+ {
+ return static_cast<MockAccessibleCaretManager*>(mManager.get());
+ }
+};
+
+// Print the name of the state for debugging.
+::std::ostream& operator<<(::std::ostream& aOstream,
+ const MockAccessibleCaretEventHub::State* aState)
+{
+ return aOstream << aState->Name();
+}
+
+class AccessibleCaretEventHubTester : public ::testing::Test
+{
+public:
+ AccessibleCaretEventHubTester()
+ {
+ DefaultValue<nsresult>::Set(NS_OK);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ // AccessibleCaretEventHub requires the caller to hold a ref to it. We just
+ // add ref here for the sake of convenience.
+ mHub.get()->AddRef();
+ }
+
+ ~AccessibleCaretEventHubTester()
+ {
+ // Release the ref added in the constructor.
+ mHub.get()->Release();
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseEvent(EventMessage aMessage,
+ nscoord aX,
+ nscoord aY)
+ {
+ auto event = MakeUnique<WidgetMouseEvent>(true, aMessage, nullptr,
+ WidgetMouseEvent::eReal);
+
+ event->button = WidgetMouseEvent::eLeftButton;
+ event->mRefPoint = LayoutDeviceIntPoint(aX, aY);
+
+ return Move(event);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMousePressEvent(nscoord aX, nscoord aY)
+ {
+ return CreateMouseEvent(eMouseDown, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseMoveEvent(nscoord aX, nscoord aY)
+ {
+ return CreateMouseEvent(eMouseMove, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseReleaseEvent(nscoord aX, nscoord aY)
+ {
+ return CreateMouseEvent(eMouseUp, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateLongTapEvent(nscoord aX, nscoord aY)
+ {
+ return CreateMouseEvent(eMouseLongTap, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchEvent(EventMessage aMessage,
+ nscoord aX,
+ nscoord aY)
+ {
+ auto event = MakeUnique<WidgetTouchEvent>(true, aMessage, nullptr);
+ int32_t identifier = 0;
+ LayoutDeviceIntPoint point(aX, aY);
+ LayoutDeviceIntPoint radius(19, 19);
+ float rotationAngle = 0;
+ float force = 1;
+
+ RefPtr<dom::Touch> touch(
+ new dom::Touch(identifier, point, radius, rotationAngle, force));
+ event->mTouches.AppendElement(touch);
+
+ return Move(event);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchStartEvent(nscoord aX, nscoord aY)
+ {
+ return CreateTouchEvent(eTouchStart, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchMoveEvent(nscoord aX, nscoord aY)
+ {
+ return CreateTouchEvent(eTouchMove, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchEndEvent(nscoord aX, nscoord aY)
+ {
+ return CreateTouchEvent(eTouchEnd, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchCancelEvent(nscoord aX, nscoord aY)
+ {
+ return CreateTouchEvent(eTouchCancel, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateWheelEvent(EventMessage aMessage)
+ {
+ auto event = MakeUnique<WidgetWheelEvent>(true, aMessage, nullptr);
+
+ return Move(event);
+ }
+
+ void HandleEventAndCheckState(UniquePtr<WidgetEvent> aEvent,
+ MockAccessibleCaretEventHub::State* aExpectedState,
+ nsEventStatus aExpectedEventStatus)
+ {
+ nsEventStatus rv = mHub->HandleEvent(aEvent.get());
+ EXPECT_EQ(mHub->GetState(), aExpectedState);
+ EXPECT_EQ(rv, aExpectedEventStatus);
+ }
+
+ void CheckState(MockAccessibleCaretEventHub::State* aExpectedState)
+ {
+ EXPECT_EQ(mHub->GetState(), aExpectedState);
+ }
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ void TestPressReleaseOnNoCaret(PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ void TestPressReleaseOnCaret(PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ void TestPressMoveReleaseOnNoCaret(PressEventCreator aPressEventCreator,
+ MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ void TestPressMoveReleaseOnCaret(PressEventCreator aPressEventCreator,
+ MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ void TestLongTapWithSelectWordSuccessful(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ void TestLongTapWithSelectWordFailed(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ void TestEventDrivenAsyncPanZoomScroll(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ // Member variables
+ RefPtr<MockAccessibleCaretEventHub> mHub{new MockAccessibleCaretEventHub()};
+
+}; // class AccessibleCaretEventHubTester
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnNoCaret)
+{
+ TestPressReleaseOnNoCaret(CreateMousePressEvent, CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnNoCaret)
+{
+ TestPressReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestPressReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()).Times(0);
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_)).Times(0);
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnCaret)
+{
+ TestPressReleaseOnCaret(CreateMousePressEvent, CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnCaret)
+{
+ TestPressReleaseOnCaret(CreateTouchStartEvent, CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestPressReleaseOnCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .Times(0);
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnNoCaret)
+{
+ TestPressMoveReleaseOnNoCaret(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnNoCaret)
+{
+ TestPressMoveReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestPressMoveReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(x0, y0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x1, y1),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A large move to simulate a dragging to select text since the distance
+ // between (x0, y0) and (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x2, y2),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnCaret)
+{
+ TestPressMoveReleaseOnCaret(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnCaret)
+{
+ TestPressMoveReleaseOnCaret(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestPressMoveReleaseOnCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_))
+ .Times(2) // two valid drag operations
+ .WillRepeatedly(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret())
+ .WillOnce(Return(NS_OK));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // A large move forms a valid drag since the distance between (x0, y0) and
+ // (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // Also a valid drag since the distance between (x0, y0) and (x3, y3) above
+ // the tolerance value even if the distance between (x2, y2) and (x3, y3) is
+ // below the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(aReleaseEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+}
+
+TEST_F(AccessibleCaretEventHubTester,
+ TestTouchStartMoveEndOnCaretWithTouchCancelIgnored)
+{
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret())
+ .WillOnce(Return(NS_OK));
+ }
+
+ // All the eTouchCancel events should be ignored in this test.
+
+ HandleEventAndCheckState(CreateTouchStartEvent(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(CreateTouchMoveEvent(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A large move forms a valid drag since the distance between (x0, y0) and
+ // (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(CreateTouchMoveEvent(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateTouchEndEvent(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordSuccessful)
+{
+ TestLongTapWithSelectWordSuccessful(CreateMousePressEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordSuccessful)
+{
+ TestLongTapWithSelectWordSuccessful(CreateTouchStartEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestLongTapWithSelectWordSuccessful(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(check, Call("longtap with scrolling"));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ // Test long tap without scrolling.
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+
+ // On Fennec, after long tap, the script might scroll and zoom the input field
+ // to the center of the screen to make typing easier before the user lifts the
+ // finger.
+ check.Call("longtap with scrolling");
+
+ HandleEventAndCheckState(aPressEventCreator(1, 1),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(1, 1),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ // Simulate scroll end fired by timer.
+ MockAccessibleCaretEventHub::FireScrollEnd(nullptr, mHub);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(1, 1),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordFailed)
+{
+ TestLongTapWithSelectWordFailed(CreateMousePressEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordFailed)
+{
+ TestLongTapWithSelectWordFailed(CreateTouchStartEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestLongTapWithSelectWordFailed(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchEventDrivenAsyncPanZoomScroll)
+{
+ TestEventDrivenAsyncPanZoomScroll(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseEventDrivenAsyncPanZoomScroll)
+{
+ TestEventDrivenAsyncPanZoomScroll(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void
+AccessibleCaretEventHubTester::TestEventDrivenAsyncPanZoomScroll(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator)
+{
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+
+ EXPECT_CALL(check, Call("1"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+
+ EXPECT_CALL(check, Call("2"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+
+ EXPECT_CALL(check, Call("3"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+
+ EXPECT_CALL(check, Call("4"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ // Receive press event.
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aMoveEventCreator(100, 100),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("1");
+
+ // Event driven scroll started
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ HandleEventAndCheckState(aMoveEventCreator(160, 160),
+ MockAccessibleCaretEventHub::ScrollState(),
+ nsEventStatus_eIgnore);
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ // Event driven scroll ended
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(210, 210),
+ MockAccessibleCaretEventHub::PostScrollState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("2");
+
+ // Receive another press event.
+ HandleEventAndCheckState(aPressEventCreator(220, 220),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aMoveEventCreator(230, 230),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("3");
+
+ // Another APZ scroll started
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ // Another APZ scroll ended
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(310, 310),
+ MockAccessibleCaretEventHub::PostScrollState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("4");
+
+ // Simulate scroll end fired by timer.
+ MockAccessibleCaretEventHub::FireScrollEnd(nullptr, mHub);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestNoEventAsyncPanZoomScroll)
+{
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(check, Call("1"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(),
+ OnScrollPositionChanged()).Times(0);
+
+ EXPECT_CALL(check, Call("2"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ check.Call("1");
+
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ check.Call("2");
+
+ // Simulate scroll end fired by timer.
+ MockAccessibleCaretEventHub::FireScrollEnd(nullptr, mHub);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollStartedThenBlur)
+{
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()).Times(0);
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur());
+ }
+
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->NotifyBlur(true);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollEndedThenBlur)
+{
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()).Times(0);
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur());
+ }
+
+ mHub->AsyncPanZoomStarted();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->ScrollPositionChanged();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ mHub->AsyncPanZoomStopped();
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::PostScrollState());
+
+ mHub->NotifyBlur(true);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+} // namespace mozilla
diff --git a/layout/base/gtest/TestAccessibleCaretManager.cpp b/layout/base/gtest/TestAccessibleCaretManager.cpp
new file mode 100644
index 000000000..78ec6eea9
--- /dev/null
+++ b/layout/base/gtest/TestAccessibleCaretManager.cpp
@@ -0,0 +1,810 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <string>
+
+#include "AccessibleCaret.h"
+#include "AccessibleCaretManager.h"
+#include "mozilla/AutoRestore.h"
+
+using ::testing::DefaultValue;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+using ::testing::_;
+
+// -----------------------------------------------------------------------------
+// This file tests CaretStateChanged events and the appearance of the two
+// AccessibleCarets manipulated by AccessibleCaretManager.
+
+namespace mozilla
+{
+using dom::CaretChangedReason;
+
+class AccessibleCaretManagerTester : public ::testing::Test
+{
+public:
+ class MockAccessibleCaret : public AccessibleCaret
+ {
+ public:
+ MockAccessibleCaret() : AccessibleCaret(nullptr) {}
+
+ virtual void SetAppearance(Appearance aAppearance) override
+ {
+ // A simplified version without touching CaretElement().
+ mAppearance = aAppearance;
+ }
+
+ virtual void SetSelectionBarEnabled(bool aEnabled) override
+ {
+ // A simplified version without touching CaretElement().
+ mSelectionBarEnabled = aEnabled;
+ }
+
+ MOCK_METHOD2(SetPosition,
+ PositionChangedResult(nsIFrame* aFrame, int32_t aOffset));
+
+ }; // class MockAccessibleCaret
+
+ class MockAccessibleCaretManager : public AccessibleCaretManager
+ {
+ public:
+ using CaretMode = AccessibleCaretManager::CaretMode;
+ using AccessibleCaretManager::UpdateCarets;
+ using AccessibleCaretManager::HideCarets;
+ using AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent;
+ using AccessibleCaretManager::sCaretsAlwaysTilt;
+ using AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling;
+
+ MockAccessibleCaretManager()
+ : AccessibleCaretManager(nullptr)
+ {
+ mFirstCaret = MakeUnique<MockAccessibleCaret>();
+ mSecondCaret = MakeUnique<MockAccessibleCaret>();
+ }
+
+ MockAccessibleCaret& FirstCaret()
+ {
+ return static_cast<MockAccessibleCaret&>(*mFirstCaret);
+ }
+
+ MockAccessibleCaret& SecondCaret()
+ {
+ return static_cast<MockAccessibleCaret&>(*mSecondCaret);
+ }
+
+ virtual bool CompareTreePosition(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame) const override
+ {
+ return true;
+ }
+
+ virtual bool IsCaretDisplayableInCursorMode(
+ nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const override
+ {
+ return true;
+ }
+
+ virtual void UpdateCaretsForOverlappingTilt() override {}
+
+ virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame)
+ {
+ if (mFirstCaret->IsVisuallyVisible()) {
+ mFirstCaret->SetAppearance(Appearance::Left);
+ }
+ if (mSecondCaret->IsVisuallyVisible()) {
+ mSecondCaret->SetAppearance(Appearance::Right);
+ }
+ }
+
+ virtual bool IsTerminated() const override { return false; }
+
+ MOCK_CONST_METHOD0(GetCaretMode, CaretMode());
+ MOCK_CONST_METHOD1(DispatchCaretStateChangedEvent,
+ void(CaretChangedReason aReason));
+ MOCK_CONST_METHOD1(HasNonEmptyTextContent, bool(nsINode* aNode));
+
+ }; // class MockAccessibleCaretManager
+
+ using Appearance = AccessibleCaret::Appearance;
+ using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+ using CaretMode = MockAccessibleCaretManager::CaretMode;
+
+ AccessibleCaretManagerTester()
+ {
+ DefaultValue<CaretMode>::Set(CaretMode::None);
+ DefaultValue<PositionChangedResult>::Set(PositionChangedResult::NotChanged);
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Changed));
+
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Changed));
+ }
+
+ AccessibleCaret::Appearance FirstCaretAppearance()
+ {
+ return mManager.FirstCaret().GetAppearance();
+ }
+
+ AccessibleCaret::Appearance SecondCaretAppearance()
+ {
+ return mManager.SecondCaret().GetAppearance();
+ }
+
+ // Member variables
+ MockAccessibleCaretManager mManager;
+
+}; // class AccessibleCaretManagerTester
+
+TEST_F(AccessibleCaretManagerTester, TestUpdatesInSelectionMode)
+{
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(3);
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnNonEmptyInput)
+{
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("mouse down"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+ EXPECT_CALL(check, Call("reflow"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+ EXPECT_CALL(check, Call("blur"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("mouse up"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("reflow2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ }
+
+ // Simulate a single tap on a non-empty input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("update");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("mouse down");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("reflow");
+
+ mManager.OnBlur();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("blur");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::MOUSEUP_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("mouse up");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("reflow2");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnEmptyInput)
+{
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("mouse down"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+ EXPECT_CALL(check, Call("reflow"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+ EXPECT_CALL(check, Call("blur"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("mouse up"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("reflow2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ }
+
+ // Simulate a single tap on an empty input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("update");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("mouse down");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("reflow");
+
+ mManager.OnBlur();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("blur");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::MOUSEUP_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("mouse up");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("reflow2");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestTypingAtEndOfInput)
+{
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("keyboard"));
+
+ // No CaretStateChanged events should be dispatched since the caret has
+ // being hidden in cursor mode.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+ }
+
+ // Simulate typing the end of the input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("update");
+
+ mManager.OnKeyboardEvent();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("keyboard");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::NO_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionMode)
+{
+ // Simulate B2G preference.
+ AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ // Initially, first caret is out of scrollport, and second caret is visible.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ // After scroll ended, first caret is visible and second caret is out of
+ // scroll port.
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ // After the scroll ended, both carets are visible.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::None);
+ check.Call("scrollstart1");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::None);
+ check.Call("scrollstart2");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionModeWithAlwaysTiltPref)
+{
+ // Simulate Firefox Android preference.
+ AutoRestore<bool> saveCaretsAlwaysTilt(
+ MockAccessibleCaretManager::sCaretsAlwaysTilt);
+ MockAccessibleCaretManager::sCaretsAlwaysTilt = true;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ // Initially, first caret is out of scrollport, and second caret is visible.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("reflow1"));
+
+ // After scroll ended, first caret is visible and second caret is out of
+ // scroll port.
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("reflow2"));
+
+ // After the scroll ended, both carets are visible.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("scrollstart1");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("reflow1");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollstart2");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("reflow2");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible)
+{
+ // Simulate B2G preference.
+ AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ // After scroll ended, the caret is out of scroll port.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ // After scroll ended, the caret is visible again.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Changed));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollstart1");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollstart2");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden)
+{
+ // Simulate B2G preference.
+ AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition)).Times(1);
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange)).Times(1);
+ EXPECT_CALL(check, Call("hidecarets"));
+
+ // After scroll ended, the caret is out of scroll port.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ // After scroll ended, the caret is visible again.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Changed));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.HideCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("hidecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeOnEmptyContent)
+{
+ // Simulate B2G preference.
+ AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
+ MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange));
+ EXPECT_CALL(check, Call("scrollstart3"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("scrollend3"));
+ }
+
+ // Simulate a single tap on an empty content.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("updatecarets");
+
+ // Scroll the caret to be out of the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart1");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ // Scroll the caret into the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart2");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend2");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart3");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend3");
+}
+
+TEST_F(AccessibleCaretManagerTester,
+ TestScrollInCursorModeWithCaretShownWhenLongTappingOnEmptyContentPref)
+{
+ // Simulate Firefox Android preference.
+ AutoRestore<bool> savesCaretShownWhenLongTappingOnEmptyContent(
+ MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent);
+ MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = true;
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("singletap updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("longtap updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll));
+ EXPECT_CALL(check, Call("longtap scrollstart1"));
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("longtap scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll));
+ EXPECT_CALL(check, Call("longtap scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("longtap scrollend2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll));
+ EXPECT_CALL(check, Call("longtap scrollstart3"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition));
+ EXPECT_CALL(check, Call("longtap scrollend3"));
+ }
+
+ // Simulate a single tap on an empty input.
+ mManager.FirstCaret().SetAppearance(Appearance::None);
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("singletap updatecarets");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ // Simulate a long tap on an empty input.
+ mManager.FirstCaret().SetAppearance(Appearance::Normal);
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap updatecarets");
+
+ // Scroll the caret to be out of the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart1");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("longtap scrollend1");
+
+ // Scroll the caret into the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart2");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap scrollend2");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart3");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap scrollend3");
+}
+
+} // namespace mozilla
diff --git a/layout/base/gtest/moz.build b/layout/base/gtest/moz.build
new file mode 100644
index 000000000..2e9100443
--- /dev/null
+++ b/layout/base/gtest/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'TestAccessibleCaretEventHub.cpp',
+ 'TestAccessibleCaretManager.cpp',
+]
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-inconsistent-missing-override']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+ '/layout/base',
+ '/layout/style',
+]
+
+# Workaround bug 1142396. Suppress the warning from gmock library for clang.
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-null-dereference']
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/layout/base/moz.build b/layout/base/moz.build
new file mode 100644
index 000000000..90fbf828c
--- /dev/null
+++ b/layout/base/moz.build
@@ -0,0 +1,233 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('ActiveLayerTracker.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('Display*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('FrameLayerBuilder.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('LayerState.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('MaskLayerImageCache.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('PaintTracker.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('nsCSSRendering.*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('nsDisplay*'):
+ BUG_COMPONENT = ('Core', 'Layout: View Rendering')
+
+with Files('Restyle*'):
+ BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
+
+with Files('nsStyle*'):
+ BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
+
+with Files('nsChangeHint.h'):
+ BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
+
+with Files('nsBidi*'):
+ BUG_COMPONENT = ('Core', 'Layout: Text')
+
+with Files('AccessibleCaret*'):
+ BUG_COMPONENT = ('Core', 'Selection')
+
+XPIDL_SOURCES += [
+ 'nsIStyleSheetService.idl',
+]
+
+if CONFIG['MOZ_DEBUG']:
+ UNIFIED_SOURCES += [
+ 'nsAutoLayoutPhase.cpp',
+ ]
+
+XPIDL_MODULE = 'layout_base'
+
+EXPORTS += [
+ 'ActiveLayerTracker.h',
+ 'CaretAssociationHint.h',
+ 'DisplayItemClip.h',
+ 'DisplayItemScrollClip.h',
+ 'DisplayListClipState.h',
+ 'FrameLayerBuilder.h',
+ 'FramePropertyTable.h',
+ 'LayerState.h',
+ 'LayoutLogging.h',
+ 'nsArenaMemoryStats.h',
+ 'nsBidi.h',
+ 'nsBidiPresUtils.h',
+ 'nsCaret.h',
+ 'nsChangeHint.h',
+ 'nsCompatibility.h',
+ 'nsCSSFrameConstructor.h',
+ 'nsDisplayItemTypes.h',
+ 'nsDisplayItemTypesList.h',
+ 'nsDisplayList.h',
+ 'nsDisplayListInvalidation.h',
+ 'nsFrameManager.h',
+ 'nsFrameManagerBase.h',
+ 'nsFrameTraversal.h',
+ 'nsIFrameTraversal.h',
+ 'nsILayoutDebugger.h',
+ 'nsILayoutHistoryState.h',
+ 'nsIPercentBSizeObserver.h',
+ 'nsIPresShell.h',
+ 'nsIReflowCallback.h',
+ 'nsLayoutUtils.h',
+ 'nsPresArena.h',
+ 'nsPresArenaObjectList.h',
+ 'nsPresContext.h',
+ 'nsPresState.h',
+ 'nsRefreshDriver.h',
+ 'nsStyleChangeList.h',
+ 'nsStyleSheetService.h',
+ 'ScrollbarStyles.h',
+ 'StackArena.h',
+ 'Units.h',
+ 'UnitTransforms.h',
+ 'WordMovementType.h',
+]
+
+EXPORTS.mozilla += [
+ 'ArenaObjectID.h',
+ 'ArenaRefPtr.h',
+ 'ArenaRefPtrInlines.h',
+ 'GeometryUtils.h',
+ 'OverflowChangedTracker.h',
+ 'PaintTracker.h',
+ 'RestyleLogging.h',
+ 'RestyleManager.h',
+ 'RestyleManagerBase.h',
+ 'RestyleManagerHandle.h',
+ 'RestyleManagerHandleInlines.h',
+ 'ServoRestyleManager.h',
+ 'StaticPresData.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AccessibleCaret.cpp',
+ 'AccessibleCaretEventHub.cpp',
+ 'AccessibleCaretManager.cpp',
+ 'ActiveLayerTracker.cpp',
+ 'DashedCornerFinder.cpp',
+ 'DisplayItemClip.cpp',
+ 'DisplayItemScrollClip.cpp',
+ 'DisplayListClipState.cpp',
+ 'DottedCornerFinder.cpp',
+ 'FrameLayerBuilder.cpp',
+ 'FramePropertyTable.cpp',
+ 'GeometryUtils.cpp',
+ 'LayoutLogging.cpp',
+ 'MaskLayerImageCache.cpp',
+ 'MobileViewportManager.cpp',
+ 'nsBidiPresUtils.cpp',
+ 'nsCaret.cpp',
+ 'nsCounterManager.cpp',
+ 'nsCSSColorUtils.cpp',
+ 'nsCSSFrameConstructor.cpp',
+ 'nsCSSRendering.cpp',
+ 'nsCSSRenderingBorders.cpp',
+ 'nsDisplayList.cpp',
+ 'nsDisplayListInvalidation.cpp',
+ 'nsDocumentViewer.cpp',
+ 'nsFrameManager.cpp',
+ 'nsFrameTraversal.cpp',
+ 'nsGenConList.cpp',
+ 'nsLayoutDebugger.cpp',
+ 'nsLayoutHistoryState.cpp',
+ 'nsLayoutUtils.cpp',
+ 'nsPresContext.cpp',
+ 'nsPresShell.cpp',
+ 'nsQuoteList.cpp',
+ 'nsStyleChangeList.cpp',
+ 'nsStyleSheetService.cpp',
+ 'PaintTracker.cpp',
+ 'PositionedEventTargeting.cpp',
+ 'RestyleManager.cpp',
+ 'RestyleManagerBase.cpp',
+ 'RestyleTracker.cpp',
+ 'ScrollbarStyles.cpp',
+ 'ServoRestyleManager.cpp',
+ 'StackArena.cpp',
+ 'StaticPresData.cpp',
+ 'TouchManager.cpp',
+ 'ZoomConstraintsClient.cpp',
+]
+
+if CONFIG['ENABLE_INTL_API']:
+ EXPORTS += [
+ 'nsBidi_ICU.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'nsBidi_ICU.cpp',
+ ]
+else:
+ EXPORTS += [
+ 'nsBidi_noICU.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'nsBidi_noICU.cpp',
+ ]
+
+# nsPresArena.cpp needs to be built separately because it uses plarena.h.
+# nsRefreshDriver.cpp needs to be built separately because of name clashes in the OS X headers
+SOURCES += [
+ 'nsPresArena.cpp',
+ 'nsRefreshDriver.cpp',
+]
+
+if CONFIG['ENABLE_TESTS']:
+ DIRS += ['gtest']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '../forms',
+ '../generic',
+ '../mathml',
+ '../printing',
+ '../style',
+ '../svg',
+ '../tables',
+ '../xul',
+ '../xul/tree/',
+ '/docshell/base',
+ '/dom/base',
+ '/dom/html',
+ '/dom/svg',
+ '/dom/xbl',
+ '/view',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ LOCAL_INCLUDES += [
+ '/widget/android',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
+MARIONETTE_LAYOUT_MANIFESTS += ['tests/marionette/manifest.ini']
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
+
+CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+if CONFIG['_MSC_VER']:
+ # This is intended as a temporary hack to support building with VS2015.
+ # 'type cast': conversion from 'unsigned int' to 'void *' of greater size
+ CXXFLAGS += ['-wd4312']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/base/nsArenaMemoryStats.h b/layout/base/nsArenaMemoryStats.h
new file mode 100644
index 000000000..ba09baaa4
--- /dev/null
+++ b/layout/base/nsArenaMemoryStats.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 nsArenaMemoryStats_h
+#define nsArenaMemoryStats_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+
+class nsTabSizes {
+public:
+ enum Kind {
+ DOM, // DOM stuff.
+ Style, // Style stuff.
+ Other // Everything else.
+ };
+
+ nsTabSizes() { mozilla::PodZero(this); }
+
+ void add(Kind kind, size_t n)
+ {
+ switch (kind) {
+ case DOM: mDom += n; break;
+ case Style: mStyle += n; break;
+ case Other: mOther += n; break;
+ default: MOZ_CRASH("bad nsTabSizes kind");
+ }
+ }
+
+ size_t mDom;
+ size_t mStyle;
+ size_t mOther;
+};
+
+#define FRAME_ID_STAT_FIELD(classname) mArena##classname
+
+struct nsArenaMemoryStats {
+#define FOR_EACH_SIZE(macro) \
+ macro(Other, mLineBoxes) \
+ macro(Style, mRuleNodes) \
+ macro(Style, mStyleContexts) \
+ macro(Style, mStyleStructs) \
+ macro(Other, mOther)
+
+ nsArenaMemoryStats()
+ :
+ #define ZERO_SIZE(kind, mSize) mSize(0),
+ FOR_EACH_SIZE(ZERO_SIZE)
+ #undef ZERO_SIZE
+ #define FRAME_ID(classname) FRAME_ID_STAT_FIELD(classname)(),
+ #include "nsFrameIdList.h"
+ #undef FRAME_ID
+ dummy()
+ {}
+
+ void addToTabSizes(nsTabSizes *sizes) const
+ {
+ #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize);
+ FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+ #undef ADD_TO_TAB_SIZES
+ #define FRAME_ID(classname) \
+ sizes->add(nsTabSizes::Other, FRAME_ID_STAT_FIELD(classname));
+ #include "nsFrameIdList.h"
+ #undef FRAME_ID
+ }
+
+ size_t getTotalSize() const
+ {
+ size_t total = 0;
+ #define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize;
+ FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE)
+ #undef ADD_TO_TOTAL_SIZE
+ #define FRAME_ID(classname) \
+ total += FRAME_ID_STAT_FIELD(classname);
+ #include "nsFrameIdList.h"
+ #undef FRAME_ID
+ return total;
+ }
+
+ #define DECL_SIZE(kind, mSize) size_t mSize;
+ FOR_EACH_SIZE(DECL_SIZE)
+ #undef DECL_SIZE
+ #define FRAME_ID(classname) size_t FRAME_ID_STAT_FIELD(classname);
+ #include "nsFrameIdList.h"
+ #undef FRAME_ID
+ int dummy; // present just to absorb the trailing comma from FRAME_ID in the
+ // constructor
+
+#undef FOR_EACH_SIZE
+};
+
+#endif // nsArenaMemoryStats_h
diff --git a/layout/base/nsAutoLayoutPhase.cpp b/layout/base/nsAutoLayoutPhase.cpp
new file mode 100644
index 000000000..a20d541f8
--- /dev/null
+++ b/layout/base/nsAutoLayoutPhase.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DEBUG
+static_assert(false, "This should not be compiled in !DEBUG");
+#endif // DEBUG
+
+#include "nsAutoLayoutPhase.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+
+nsAutoLayoutPhase::nsAutoLayoutPhase(nsPresContext* aPresContext,
+ nsLayoutPhase aPhase)
+ : mPresContext(aPresContext)
+ , mPhase(aPhase)
+ , mCount(0)
+{
+ Enter();
+}
+
+nsAutoLayoutPhase::~nsAutoLayoutPhase()
+{
+ Exit();
+ MOZ_ASSERT(mCount == 0, "imbalanced");
+}
+
+void
+nsAutoLayoutPhase::Enter()
+{
+ switch (mPhase) {
+ case eLayoutPhase_Paint:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Paint] == 0,
+ "recurring into paint");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Reflow] == 0,
+ "painting in the middle of reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_FrameC] == 0,
+ "painting in the middle of frame construction");
+ break;
+ case eLayoutPhase_Reflow:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Paint] == 0,
+ "reflowing in the middle of a paint");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Reflow] == 0,
+ "recurring into reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_FrameC] == 0,
+ "reflowing in the middle of frame construction");
+ break;
+ case eLayoutPhase_FrameC:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Paint] == 0,
+ "constructing frames in the middle of a paint");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_Reflow] == 0,
+ "constructing frames in the middle of reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[eLayoutPhase_FrameC] == 0,
+ "recurring into frame construction");
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "constructing frames and scripts are not blocked");
+ break;
+ case eLayoutPhase_COUNT:
+ break;
+ }
+
+ ++(mPresContext->mLayoutPhaseCount[mPhase]);
+ ++mCount;
+}
+
+void
+nsAutoLayoutPhase::Exit()
+{
+ MOZ_ASSERT(mCount > 0 && mPresContext->mLayoutPhaseCount[mPhase] > 0,
+ "imbalanced");
+ --(mPresContext->mLayoutPhaseCount[mPhase]);
+ --mCount;
+}
diff --git a/layout/base/nsAutoLayoutPhase.h b/layout/base/nsAutoLayoutPhase.h
new file mode 100644
index 000000000..4145afd65
--- /dev/null
+++ b/layout/base/nsAutoLayoutPhase.h
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAutoLayoutPhase_h
+#define nsAutoLayoutPhase_h
+
+#ifdef DEBUG
+
+// We can't forward declare an enum before C++11 which means we have to include
+// nsPresContext.h just because nsLayoutPhase is passed to the ctor.
+#include "nsPresContext.h"
+
+struct nsAutoLayoutPhase {
+ nsAutoLayoutPhase(nsPresContext* aPresContext, nsLayoutPhase aPhase);
+ ~nsAutoLayoutPhase();
+
+ void Enter();
+ void Exit();
+
+private:
+ nsPresContext* mPresContext;
+ nsLayoutPhase mPhase;
+ uint32_t mCount;
+};
+
+#define AUTO_LAYOUT_PHASE_ENTRY_POINT(pc_, phase_) \
+ nsAutoLayoutPhase autoLayoutPhase((pc_), (eLayoutPhase_##phase_))
+#define LAYOUT_PHASE_TEMP_EXIT() \
+ PR_BEGIN_MACRO \
+ autoLayoutPhase.Exit(); \
+ PR_END_MACRO
+#define LAYOUT_PHASE_TEMP_REENTER() \
+ PR_BEGIN_MACRO \
+ autoLayoutPhase.Enter(); \
+ PR_END_MACRO
+
+#else // DEBUG
+
+#define AUTO_LAYOUT_PHASE_ENTRY_POINT(pc_, phase_) \
+ PR_BEGIN_MACRO PR_END_MACRO
+#define LAYOUT_PHASE_TEMP_EXIT() \
+ PR_BEGIN_MACRO PR_END_MACRO
+#define LAYOUT_PHASE_TEMP_REENTER() \
+ PR_BEGIN_MACRO PR_END_MACRO
+
+#endif // DEBUG
+
+#endif // nsAutoLayoutPhase_h
diff --git a/layout/base/nsBidi.h b/layout/base/nsBidi.h
new file mode 100644
index 000000000..c7c367092
--- /dev/null
+++ b/layout/base/nsBidi.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidi_h__
+#define nsBidi_h__
+
+#if ENABLE_INTL_API
+#include "nsBidi_ICU.h"
+#else
+#include "nsBidi_noICU.h"
+#endif
+
+#endif // _nsBidi_h_
diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp
new file mode 100644
index 000000000..b3c20aabb
--- /dev/null
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -0,0 +1,2297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/IntegerRange.h"
+
+#include "nsAutoPtr.h"
+#include "nsBidiPresUtils.h"
+#include "nsFontMetrics.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsBidiUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsContainerFrame.h"
+#include "nsInlineFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsFirstLetterFrame.h"
+#include "nsUnicodeProperties.h"
+#include "nsTextFrame.h"
+#include "nsBlockFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleStructInlines.h"
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include <algorithm>
+
+#undef NOISY_BIDI
+#undef REALLY_NOISY_BIDI
+
+using namespace mozilla;
+
+static const char16_t kSpace = 0x0020;
+static const char16_t kZWSP = 0x200B;
+static const char16_t kLineSeparator = 0x2028;
+static const char16_t kObjectSubstitute = 0xFFFC;
+static const char16_t kLRE = 0x202A;
+static const char16_t kRLE = 0x202B;
+static const char16_t kLRO = 0x202D;
+static const char16_t kRLO = 0x202E;
+static const char16_t kPDF = 0x202C;
+static const char16_t kLRI = 0x2066;
+static const char16_t kRLI = 0x2067;
+static const char16_t kFSI = 0x2068;
+static const char16_t kPDI = 0x2069;
+static const char16_t kSeparators[] = {
+ // All characters with Bidi type Segment Separator or Block Separator
+ char16_t('\t'),
+ char16_t('\r'),
+ char16_t('\n'),
+ char16_t(0xb),
+ char16_t(0x1c),
+ char16_t(0x1d),
+ char16_t(0x1e),
+ char16_t(0x1f),
+ char16_t(0x85),
+ char16_t(0x2029),
+ char16_t(0)
+};
+
+#define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
+
+static bool
+IsIsolateControl(char16_t aChar)
+{
+ return aChar == kLRI || aChar == kRLI || aChar == kFSI;
+}
+
+// Given a style context, return any bidi control character necessary to
+// implement style properties that override directionality (i.e. if it has
+// unicode-bidi:bidi-override, or text-orientation:upright in vertical
+// writing mode) when applying the bidi algorithm.
+//
+// Returns 0 if no override control character is implied by this style.
+static char16_t
+GetBidiOverride(nsStyleContext* aStyleContext)
+{
+ const nsStyleVisibility* vis = aStyleContext->StyleVisibility();
+ if ((vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_RL ||
+ vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_LR) &&
+ vis->mTextOrientation == NS_STYLE_TEXT_ORIENTATION_UPRIGHT) {
+ return kLRO;
+ }
+ const nsStyleTextReset* text = aStyleContext->StyleTextReset();
+ if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
+ return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLO : kLRO;
+ }
+ return 0;
+}
+
+// Given a style context, return any bidi control character necessary to
+// implement style properties that affect bidi resolution (i.e. if it
+// has unicode-bidiembed, isolate, or plaintext) when applying the bidi
+// algorithm.
+//
+// Returns 0 if no control character is implied by the style.
+//
+// Note that GetBidiOverride and GetBidiControl need to be separate
+// because in the case of unicode-bidi:isolate-override we need both
+// FSI and LRO/RLO.
+static char16_t
+GetBidiControl(nsStyleContext* aStyleContext)
+{
+ const nsStyleVisibility* vis = aStyleContext->StyleVisibility();
+ const nsStyleTextReset* text = aStyleContext->StyleTextReset();
+ if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) {
+ return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLE : kLRE;
+ }
+ if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE) {
+ if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
+ // isolate-override
+ return kFSI;
+ }
+ // <bdi> element already has its directionality set from content so
+ // we never need to return kFSI.
+ return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLI : kLRI;
+ }
+ if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
+ return kFSI;
+ }
+ return 0;
+}
+
+struct BidiParagraphData {
+ nsString mBuffer;
+ AutoTArray<char16_t, 16> mEmbeddingStack;
+ nsTArray<nsIFrame*> mLogicalFrames;
+ nsTArray<nsLineBox*> mLinePerFrame;
+ nsDataHashtable<nsISupportsHashKey, int32_t> mContentToFrameIndex;
+ // Cached presentation context for the frames we're processing.
+ nsPresContext* mPresContext;
+ bool mIsVisual;
+ nsBidiLevel mParaLevel;
+ nsIContent* mPrevContent;
+ nsAutoPtr<nsBidi> mBidiEngine;
+ nsIFrame* mPrevFrame;
+#ifdef DEBUG
+ // Only used for NOISY debug output.
+ nsBlockFrame* mCurrentBlock;
+#endif
+
+ void Init(nsBlockFrame* aBlockFrame)
+ {
+ mBidiEngine = new nsBidi();
+ mPrevContent = nullptr;
+#ifdef DEBUG
+ mCurrentBlock = aBlockFrame;
+#endif
+
+ mParaLevel = nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->StyleContext());
+
+ mPresContext = aBlockFrame->PresContext();
+ mIsVisual = mPresContext->IsVisualMode();
+ if (mIsVisual) {
+ /**
+ * Drill up in content to detect whether this is an element that needs to
+ * be rendered with logical order even on visual pages.
+ *
+ * We always use logical order on form controls, firstly so that text
+ * entry will be in logical order, but also because visual pages were
+ * written with the assumption that even if the browser had no support
+ * for right-to-left text rendering, it would use native widgets with
+ * bidi support to display form controls.
+ *
+ * We also use logical order in XUL elements, since we expect that if a
+ * XUL element appears in a visual page, it will be generated by an XBL
+ * binding and contain localized text which will be in logical order.
+ */
+ for (nsIContent* content = aBlockFrame->GetContent() ; content;
+ content = content->GetParent()) {
+ if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) ||
+ content->IsXULElement()) {
+ mIsVisual = false;
+ break;
+ }
+ }
+ }
+ }
+
+ nsresult SetPara()
+ {
+ return mBidiEngine->SetPara(mBuffer.get(), BufferLength(),
+ mParaLevel);
+ }
+
+ /**
+ * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL.
+ * GetParaLevel() returns the actual (resolved) paragraph level which is
+ * always either NSBIDI_LTR or NSBIDI_RTL
+ */
+ nsBidiLevel GetParaLevel()
+ {
+ nsBidiLevel paraLevel = mParaLevel;
+ if (paraLevel == NSBIDI_DEFAULT_LTR || paraLevel == NSBIDI_DEFAULT_RTL) {
+ mBidiEngine->GetParaLevel(&paraLevel);
+ }
+ return paraLevel;
+ }
+
+ nsBidiDirection GetDirection()
+ {
+ nsBidiDirection dir;
+ mBidiEngine->GetDirection(&dir);
+ return dir;
+ }
+
+ nsresult CountRuns(int32_t *runCount){ return mBidiEngine->CountRuns(runCount); }
+
+ nsresult GetLogicalRun(int32_t aLogicalStart,
+ int32_t* aLogicalLimit,
+ nsBidiLevel* aLevel)
+ {
+ nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart,
+ aLogicalLimit, aLevel);
+ if (mIsVisual || NS_FAILED(rv))
+ *aLevel = GetParaLevel();
+ return rv;
+ }
+
+ void ResetData()
+ {
+ mLogicalFrames.Clear();
+ mLinePerFrame.Clear();
+ mContentToFrameIndex.Clear();
+ mBuffer.SetLength(0);
+ mPrevContent = nullptr;
+ for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
+ mBuffer.Append(mEmbeddingStack[i]);
+ mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
+ mLinePerFrame.AppendElement((nsLineBox*)nullptr);
+ }
+ }
+
+ void AppendFrame(nsIFrame* aFrame,
+ nsBlockInFlowLineIterator* aLineIter,
+ nsIContent* aContent = nullptr)
+ {
+ if (aContent) {
+ mContentToFrameIndex.Put(aContent, FrameCount());
+ }
+ mLogicalFrames.AppendElement(aFrame);
+
+ AdvanceLineIteratorToFrame(aFrame, aLineIter, mPrevFrame);
+ mLinePerFrame.AppendElement(aLineIter->GetLine().get());
+ }
+
+ void AdvanceAndAppendFrame(nsIFrame** aFrame,
+ nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame** aNextSibling)
+ {
+ nsIFrame* frame = *aFrame;
+ nsIFrame* nextSibling = *aNextSibling;
+
+ frame = frame->GetNextContinuation();
+ if (frame) {
+ AppendFrame(frame, aLineIter, nullptr);
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+ }
+
+ *aFrame = frame;
+ *aNextSibling = nextSibling;
+ }
+
+ int32_t GetLastFrameForContent(nsIContent *aContent)
+ {
+ int32_t index = 0;
+ mContentToFrameIndex.Get(aContent, &index);
+ return index;
+ }
+
+ int32_t FrameCount(){ return mLogicalFrames.Length(); }
+
+ int32_t BufferLength(){ return mBuffer.Length(); }
+
+ nsIFrame* FrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; }
+
+ nsLineBox* GetLineForFrameAt(int32_t aIndex){ return mLinePerFrame[aIndex]; }
+
+ void AppendUnichar(char16_t aCh){ mBuffer.Append(aCh); }
+
+ void AppendString(const nsDependentSubstring& aString){ mBuffer.Append(aString); }
+
+ void AppendControlChar(char16_t aCh)
+ {
+ mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
+ mLinePerFrame.AppendElement((nsLineBox*)nullptr);
+ AppendUnichar(aCh);
+ }
+
+ void PushBidiControl(char16_t aCh)
+ {
+ AppendControlChar(aCh);
+ mEmbeddingStack.AppendElement(aCh);
+ }
+
+ void AppendPopChar(char16_t aCh)
+ {
+ AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
+ }
+
+ void PopBidiControl(char16_t aCh)
+ {
+ MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
+ MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
+ AppendPopChar(aCh);
+ mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1);
+ }
+
+ void ClearBidiControls()
+ {
+ for (char16_t c : Reversed(mEmbeddingStack)) {
+ AppendPopChar(c);
+ }
+ }
+
+ static bool
+ IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame* aPrevFrame, nsIFrame* aFrame)
+ {
+ nsIFrame* endFrame = aLineIter->IsLastLineInList() ? nullptr :
+ aLineIter->GetLine().next()->mFirstChild;
+ nsIFrame* startFrame = aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
+ for (nsIFrame* frame = startFrame; frame && frame != endFrame;
+ frame = frame->GetNextSibling()) {
+ if (frame == aFrame)
+ return true;
+ }
+ return false;
+ }
+
+ static void
+ AdvanceLineIteratorToFrame(nsIFrame* aFrame,
+ nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame*& aPrevFrame)
+ {
+ // Advance aLine to the line containing aFrame
+ nsIFrame* child = aFrame;
+ nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ while (parent && !nsLayoutUtils::GetAsBlock(parent)) {
+ child = parent;
+ parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ }
+ NS_ASSERTION (parent, "aFrame is not a descendent of a block frame");
+ while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) {
+#ifdef DEBUG
+ bool hasNext =
+#endif
+ aLineIter->Next();
+ NS_ASSERTION(hasNext, "Can't find frame in lines!");
+ aPrevFrame = nullptr;
+ }
+ aPrevFrame = child;
+ }
+
+};
+
+struct BidiLineData {
+ nsTArray<nsIFrame*> mLogicalFrames;
+ nsTArray<nsIFrame*> mVisualFrames;
+ nsTArray<int32_t> mIndexMap;
+ AutoTArray<uint8_t, 18> mLevels;
+ bool mIsReordered;
+
+ BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine)
+ {
+ /**
+ * Initialize the logically-ordered array of frames using the top-level
+ * frames of a single line
+ */
+ mLogicalFrames.Clear();
+
+ bool isReordered = false;
+ bool hasRTLFrames = false;
+ bool hasVirtualControls = false;
+
+ auto appendFrame = [&](nsIFrame* frame, nsBidiLevel level) {
+ mLogicalFrames.AppendElement(frame);
+ mLevels.AppendElement(level);
+ mIndexMap.AppendElement(0);
+ if (IS_LEVEL_RTL(level)) {
+ hasRTLFrames = true;
+ }
+ };
+
+ bool firstFrame = true;
+ for (nsIFrame* frame = aFirstFrameOnLine;
+ frame && aNumFramesOnLine--;
+ frame = frame->GetNextSibling()) {
+ FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
+ // Ignore virtual control before the first frame. Doing so should
+ // not affect the visual result, but could avoid running into the
+ // stripping code below for many cases.
+ if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) {
+ appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
+ hasVirtualControls = true;
+ }
+ appendFrame(frame, bidiData.embeddingLevel);
+ firstFrame = false;
+ }
+
+ // Reorder the line
+ nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(),
+ mIndexMap.Elements());
+
+ // Strip virtual frames
+ if (hasVirtualControls) {
+ auto originalCount = mLogicalFrames.Length();
+ nsTArray<int32_t> realFrameMap(originalCount);
+ size_t count = 0;
+ for (auto i : MakeRange(originalCount)) {
+ if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) {
+ realFrameMap.AppendElement(-1);
+ } else {
+ mLogicalFrames[count] = mLogicalFrames[i];
+ mLevels[count] = mLevels[i];
+ realFrameMap.AppendElement(count);
+ count++;
+ }
+ }
+ // Only keep index map for real frames.
+ for (size_t i = 0, j = 0; i < originalCount; ++i) {
+ auto newIndex = realFrameMap[mIndexMap[i]];
+ if (newIndex != -1) {
+ mIndexMap[j] = newIndex;
+ j++;
+ }
+ }
+ mLogicalFrames.TruncateLength(count);
+ mLevels.TruncateLength(count);
+ mIndexMap.TruncateLength(count);
+ }
+
+ for (int32_t i = 0; i < FrameCount(); i++) {
+ mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
+ if (i != mIndexMap[i]) {
+ isReordered = true;
+ }
+ }
+
+ // If there's an RTL frame, assume the line is reordered
+ mIsReordered = isReordered || hasRTLFrames;
+ }
+
+ int32_t FrameCount(){ return mLogicalFrames.Length(); }
+
+ nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; }
+
+ nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; }
+};
+
+#ifdef DEBUG
+extern "C" {
+void MOZ_EXPORT
+DumpFrameArray(const nsTArray<nsIFrame*>& aFrames)
+{
+ for (nsIFrame* frame : aFrames) {
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ fprintf_stderr(stderr, "(Bidi control frame)\n");
+ } else {
+ frame->List();
+ }
+ }
+}
+
+void MOZ_EXPORT
+DumpBidiLine(BidiLineData* aData, bool aVisualOrder)
+{
+ DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames);
+}
+}
+#endif
+
+/* Some helper methods for Resolve() */
+
+// Should this frame be split between text runs?
+static bool
+IsBidiSplittable(nsIFrame* aFrame)
+{
+ // Bidi inline containers should be split, unless they're line frames.
+ nsIAtom* frameType = aFrame->GetType();
+ return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) &&
+ frameType != nsGkAtoms::lineFrame) ||
+ frameType == nsGkAtoms::textFrame;
+}
+
+// Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
+static bool
+IsBidiLeaf(nsIFrame* aFrame)
+{
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer);
+}
+
+/**
+ * Create non-fluid continuations for the ancestors of a given frame all the way
+ * up the frame tree until we hit a non-splittable frame (a line or a block).
+ *
+ * @param aParent the first parent frame to be split
+ * @param aFrame the child frames after this frame are reparented to the
+ * newly-created continuation of aParent.
+ * If aFrame is null, all the children of aParent are reparented.
+ */
+static nsresult
+SplitInlineAncestors(nsContainerFrame* aParent,
+ nsIFrame* aFrame)
+{
+ nsPresContext* presContext = aParent->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+ nsIFrame* frame = aFrame;
+ nsContainerFrame* parent = aParent;
+ nsContainerFrame* newParent;
+
+ while (IsBidiSplittable(parent)) {
+ nsContainerFrame* grandparent = parent->GetParent();
+ NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors");
+
+ // Split the child list after |frame|, unless it is the last child.
+ if (!frame || frame->GetNextSibling()) {
+
+ newParent = static_cast<nsContainerFrame*>(presShell->FrameConstructor()->
+ CreateContinuingFrame(presContext, parent, grandparent, false));
+
+ nsFrameList tail = parent->StealFramesAfter(frame);
+
+ // Reparent views as necessary
+ nsresult rv;
+ rv = nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The parent's continuation adopts the siblings after the split.
+ newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail);
+
+ // The list name kNoReflowPrincipalList would indicate we don't want reflow
+ nsFrameList temp(newParent, newParent);
+ grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp);
+ }
+
+ frame = parent;
+ parent = grandparent;
+ }
+
+ return NS_OK;
+}
+
+static void
+MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext)
+{
+ NS_ASSERTION (!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
+ "next-in-flow is not next continuation!");
+ aFrame->SetNextInFlow(aNext);
+
+ NS_ASSERTION (!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
+ "prev-in-flow is not prev continuation!");
+ aNext->SetPrevInFlow(aFrame);
+}
+
+static void
+MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame, nsIFrame* aNext)
+{
+ nsIFrame* frame;
+ nsIFrame* next;
+
+ for (frame = aFrame, next = aNext;
+ frame && next &&
+ next != frame && next == frame->GetNextInFlow() &&
+ IsBidiSplittable(frame);
+ frame = frame->GetParent(), next = next->GetParent()) {
+
+ frame->SetNextContinuation(next);
+ next->SetPrevContinuation(frame);
+ }
+}
+
+// If aFrame is the last child of its parent, convert bidi continuations to
+// fluid continuations for all of its inline ancestors.
+// If it isn't the last child, make sure that its continuation is fluid.
+static void
+JoinInlineAncestors(nsIFrame* aFrame)
+{
+ nsIFrame* frame = aFrame;
+ do {
+ nsIFrame* next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ }
+ // Join the parent only as long as we're its last child.
+ if (frame->GetNextSibling())
+ break;
+ frame = frame->GetParent();
+ } while (frame && IsBidiSplittable(frame));
+}
+
+static nsresult
+CreateContinuation(nsIFrame* aFrame,
+ nsIFrame** aNewFrame,
+ bool aIsFluid)
+{
+ NS_PRECONDITION(aNewFrame, "null OUT ptr");
+ NS_PRECONDITION(aFrame, "null ptr");
+
+ *aNewFrame = nullptr;
+
+ nsPresContext *presContext = aFrame->PresContext();
+ nsIPresShell *presShell = presContext->PresShell();
+ NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateContinuation");
+
+ nsContainerFrame* parent = aFrame->GetParent();
+ NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
+
+ nsresult rv = NS_OK;
+
+ // Have to special case floating first letter frames because the continuation
+ // doesn't go in the first letter frame. The continuation goes with the rest
+ // of the text that the first letter frame was made out of.
+ if (parent->GetType() == nsGkAtoms::letterFrame &&
+ parent->IsFloating()) {
+ nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
+ rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame,
+ aNewFrame, aIsFluid);
+ return rv;
+ }
+
+ *aNewFrame = presShell->FrameConstructor()->
+ CreateContinuingFrame(presContext, aFrame, parent, aIsFluid);
+
+ // The list name kNoReflowPrincipalList would indicate we don't want reflow
+ // XXXbz this needs higher-level framelist love
+ nsFrameList temp(*aNewFrame, *aNewFrame);
+ parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp);
+
+ if (!aIsFluid) {
+ // Split inline ancestor frames
+ rv = SplitInlineAncestors(parent, aFrame);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Overview of the implementation of Resolve():
+ *
+ * Walk through the descendants of aBlockFrame and build:
+ * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
+ * * mBuffer: an nsString containing a representation of
+ * the content of the frames.
+ * In the case of text frames, this is the actual text context of the
+ * frames, but some other elements are represented in a symbolic form which
+ * will make the Unicode Bidi Algorithm give the correct results.
+ * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
+ * elements are represented by the corresponding Unicode control characters.
+ * <br> elements are represented by U+2028 LINE SEPARATOR
+ * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
+ * CHARACTER
+ *
+ * Then pass mBuffer to the Bidi engine for resolving of embedding levels
+ * by nsBidi::SetPara() and division into directional runs by
+ * nsBidi::CountRuns().
+ *
+ * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
+ * correlate them with the frames indexed in mLogicalFrames, setting the
+ * baseLevel and embeddingLevel properties according to the results returned
+ * by the Bidi engine.
+ *
+ * The rendering layer requires each text frame to contain text in only one
+ * direction, so we may need to call EnsureBidiContinuation() to split frames.
+ * We may also need to call RemoveBidiContinuation() to convert frames created
+ * by EnsureBidiContinuation() in previous reflows into fluid continuations.
+ */
+nsresult
+nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame)
+{
+ BidiParagraphData bpd;
+ bpd.Init(aBlockFrame);
+
+ // Handle bidi-override being set on the block itself before calling
+ // TraverseFrames.
+ // No need to call GetBidiControl as well, because isolate and embed
+ // values of unicode-bidi property are redundant on block elements.
+ // unicode-bidi:plaintext on a block element is handled by block frame
+ // via using nsIFrame::GetWritingMode(nsIFrame*).
+ char16_t ch = GetBidiOverride(aBlockFrame->StyleContext());
+ if (ch != 0) {
+ bpd.PushBidiControl(ch);
+ }
+ for (nsBlockFrame* block = aBlockFrame; block;
+ block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
+#ifdef DEBUG
+ bpd.mCurrentBlock = block;
+#endif
+ block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ nsBlockInFlowLineIterator it(block, block->LinesBegin());
+ bpd.mPrevFrame = nullptr;
+ TraverseFrames(&it, block->PrincipalChildList().FirstChild(), &bpd);
+ nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
+ if (overflowLines) {
+ nsBlockInFlowLineIterator it(block, overflowLines->mLines.begin(), true);
+ bpd.mPrevFrame = nullptr;
+ TraverseFrames(&it, overflowLines->mFrames.FirstChild(), &bpd);
+ }
+ }
+
+ if (ch != 0) {
+ bpd.PopBidiControl(ch);
+ }
+
+ return ResolveParagraph(&bpd);
+}
+
+nsresult
+nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd)
+{
+ if (aBpd->BufferLength() < 1) {
+ return NS_OK;
+ }
+ aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
+
+ int32_t runCount;
+
+ nsresult rv = aBpd->SetPara();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsBidiLevel embeddingLevel = aBpd->GetParaLevel();
+
+ rv = aBpd->CountRuns(&runCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t runLength = 0; // the length of the current run of text
+ int32_t logicalLimit = 0; // the end of the current run + 1
+ int32_t numRun = -1;
+ int32_t fragmentLength = 0; // the length of the current text frame
+ int32_t frameIndex = -1; // index to the frames in mLogicalFrames
+ int32_t frameCount = aBpd->FrameCount();
+ int32_t contentOffset = 0; // offset of current frame in its content node
+ bool isTextFrame = false;
+ nsIFrame* frame = nullptr;
+ nsIContent* content = nullptr;
+ int32_t contentTextLength = 0;
+
+ FramePropertyTable* propTable = aBpd->mPresContext->PropertyTable();
+ nsLineBox* currentLine = nullptr;
+
+#ifdef DEBUG
+#ifdef NOISY_BIDI
+ printf("Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, runCount=%d\n",
+ (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), frameCount, runCount);
+#ifdef REALLY_NOISY_BIDI
+ printf(" block frame tree=:\n");
+ aBpd->mCurrentBlock->List(stdout, 0);
+#endif
+#endif
+#endif
+
+ if (runCount == 1 && frameCount == 1 &&
+ aBpd->GetDirection() == NSBIDI_LTR && aBpd->GetParaLevel() == 0) {
+ // We have a single left-to-right frame in a left-to-right paragraph,
+ // without bidi isolation from the surrounding text.
+ // Make sure that the embedding level and base level frame properties aren't
+ // set (because if they are this frame used to have some other direction,
+ // so we can't do this optimization), and we're done.
+ nsIFrame* frame = aBpd->FrameAt(0);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ FrameBidiData bidiData = frame->GetBidiData();
+ if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
+#ifdef DEBUG
+#ifdef NOISY_BIDI
+ printf("early return for single direction frame %p\n", (void*)frame);
+#endif
+#endif
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsIFrame* lastRealFrame = nullptr;
+ nsBidiLevel lastEmbedingLevel = kBidiLevelNone;
+ nsBidiLevel precedingControl = kBidiLevelNone;
+
+ auto storeBidiDataToFrame = [&]() {
+ FrameBidiData bidiData;
+ bidiData.embeddingLevel = embeddingLevel;
+ bidiData.baseLevel = aBpd->GetParaLevel();
+ // If a control character doesn't have a lower embedding level than
+ // both the preceding and the following frame, it isn't something
+ // needed for getting the correct result. This optimization should
+ // remove almost all of embeds and overrides, and some of isolates.
+ if (precedingControl >= embeddingLevel ||
+ precedingControl >= lastEmbedingLevel) {
+ bidiData.precedingControl = kBidiLevelNone;
+ } else {
+ bidiData.precedingControl = precedingControl;
+ }
+ precedingControl = kBidiLevelNone;
+ lastEmbedingLevel = embeddingLevel;
+ propTable->Set(frame, nsIFrame::BidiDataProperty(), bidiData);
+ };
+
+ for (; ;) {
+ if (fragmentLength <= 0) {
+ // Get the next frame from mLogicalFrames
+ if (++frameIndex >= frameCount) {
+ break;
+ }
+ frame = aBpd->FrameAt(frameIndex);
+ if (frame == NS_BIDI_CONTROL_FRAME ||
+ nsGkAtoms::textFrame != frame->GetType()) {
+ /*
+ * Any non-text frame corresponds to a single character in the text buffer
+ * (a bidi control character, LINE SEPARATOR, or OBJECT SUBSTITUTE)
+ */
+ isTextFrame = false;
+ fragmentLength = 1;
+ } else {
+ currentLine = aBpd->GetLineForFrameAt(frameIndex);
+ content = frame->GetContent();
+ if (!content) {
+ rv = NS_OK;
+ break;
+ }
+ contentTextLength = content->TextLength();
+ if (contentTextLength == 0) {
+ frame->AdjustOffsetsForBidi(0, 0);
+ // Set the base level and embedding level of the current run even
+ // on an empty frame. Otherwise frame reordering will not be correct.
+ storeBidiDataToFrame();
+ continue;
+ }
+ int32_t start, end;
+ frame->GetOffsets(start, end);
+ NS_ASSERTION(!(contentTextLength < end - start),
+ "Frame offsets don't fit in content");
+ fragmentLength = std::min(contentTextLength, end - start);
+ contentOffset = start;
+ isTextFrame = true;
+ }
+ } // if (fragmentLength <= 0)
+
+ if (runLength <= 0) {
+ // Get the next run of text from the Bidi engine
+ if (++numRun >= runCount) {
+ break;
+ }
+ int32_t lineOffset = logicalLimit;
+ if (NS_FAILED(aBpd->GetLogicalRun(
+ lineOffset, &logicalLimit, &embeddingLevel) ) ) {
+ break;
+ }
+ runLength = logicalLimit - lineOffset;
+ } // if (runLength <= 0)
+
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ // In theory, we only need to do this for isolates. However, it is
+ // easier to do this for all here because we do not maintain the
+ // index to get corresponding character from buffer. Since we do
+ // have proper embedding level for all those characters, including
+ // them wouldn't affect the final result.
+ precedingControl = std::min(precedingControl, embeddingLevel);
+ }
+ else {
+ storeBidiDataToFrame();
+ if (isTextFrame) {
+ if ( (runLength > 0) && (runLength < fragmentLength) ) {
+ /*
+ * The text in this frame continues beyond the end of this directional run.
+ * Create a non-fluid continuation frame for the next directional run.
+ */
+ currentLine->MarkDirty();
+ nsIFrame* nextBidi;
+ int32_t runEnd = contentOffset + runLength;
+ rv = EnsureBidiContinuation(frame, &nextBidi, contentOffset, runEnd);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ nextBidi->AdjustOffsetsForBidi(runEnd,
+ contentOffset + fragmentLength);
+ frame = nextBidi;
+ contentOffset = runEnd;
+ } // if (runLength < fragmentLength)
+ else {
+ if (contentOffset + fragmentLength == contentTextLength) {
+ /*
+ * We have finished all the text in this content node. Convert any
+ * further non-fluid continuations to fluid continuations and advance
+ * frameIndex to the last frame in the content node
+ */
+ int32_t newIndex = aBpd->GetLastFrameForContent(content);
+ if (newIndex > frameIndex) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ frameIndex = newIndex;
+ frame = aBpd->FrameAt(frameIndex);
+ }
+ } else if (fragmentLength > 0 && runLength > fragmentLength) {
+ /*
+ * There is more text that belongs to this directional run in the next
+ * text frame: make sure it is a fluid continuation of the current frame.
+ * Do not advance frameIndex, because the next frame may contain
+ * multi-directional text and need to be split
+ */
+ int32_t newIndex = frameIndex;
+ do {
+ } while (++newIndex < frameCount &&
+ aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
+ if (newIndex < frameCount) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ }
+ } else if (runLength == fragmentLength) {
+ /*
+ * If the directional run ends at the end of the frame, make sure
+ * that any continuation is non-fluid, and do the same up the
+ * parent chain
+ */
+ nsIFrame* next = frame->GetNextInFlow();
+ if (next) {
+ currentLine->MarkDirty();
+ MakeContinuationsNonFluidUpParentChain(frame, next);
+ }
+ }
+ frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength);
+ }
+ } // isTextFrame
+ } // not bidi control frame
+ int32_t temp = runLength;
+ runLength -= fragmentLength;
+ fragmentLength -= temp;
+
+ // Record last real frame so that we can do spliting properly even
+ // if a run ends after a virtual bidi control frame.
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ lastRealFrame = frame;
+ }
+ if (lastRealFrame && fragmentLength <= 0) {
+ // If the frame is at the end of a run, and this is not the end of our
+ // paragrah, split all ancestor inlines that need splitting.
+ // To determine whether we're at the end of the run, we check that we've
+ // finished processing the current run, and that the current frame
+ // doesn't have a fluid continuation (it could have a fluid continuation
+ // of zero length, so testing runLength alone is not sufficient).
+ if (runLength <= 0 && !lastRealFrame->GetNextInFlow()) {
+ if (numRun + 1 < runCount) {
+ nsIFrame* child = lastRealFrame;
+ nsContainerFrame* parent = child->GetParent();
+ // As long as we're on the last sibling, the parent doesn't have to
+ // be split.
+ // However, if the parent has a fluid continuation, we do have to make
+ // it non-fluid. This can happen e.g. when we have a first-letter
+ // frame and the end of the first-letter coincides with the end of a
+ // directional run.
+ while (parent &&
+ IsBidiSplittable(parent) &&
+ !child->GetNextSibling()) {
+ nsIFrame* next = parent->GetNextInFlow();
+ if (next) {
+ parent->SetNextContinuation(next);
+ next->SetPrevContinuation(parent);
+ }
+ child = parent;
+ parent = child->GetParent();
+ }
+ if (parent && IsBidiSplittable(parent)) {
+ SplitInlineAncestors(parent, child);
+ }
+ }
+ } else if (frame != NS_BIDI_CONTROL_FRAME) {
+ // We're not at an end of a run. If |frame| is the last child of its
+ // parent, and its ancestors happen to have bidi continuations, convert
+ // them into fluid continuations.
+ JoinInlineAncestors(frame);
+ }
+ }
+ } // for
+
+#ifdef DEBUG
+#ifdef REALLY_NOISY_BIDI
+ printf("---\nAfter Resolve(), frameTree =:\n");
+ aBpd->mCurrentBlock->List(stdout, 0);
+ printf("===\n");
+#endif
+#endif
+
+ return rv;
+}
+
+void
+nsBidiPresUtils::TraverseFrames(nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame* aCurrentFrame,
+ BidiParagraphData* aBpd)
+{
+ if (!aCurrentFrame)
+ return;
+
+#ifdef DEBUG
+ nsBlockFrame* initialLineContainer = aLineIter->GetContainer();
+#endif
+
+ nsIFrame* childFrame = aCurrentFrame;
+ do {
+ /*
+ * It's important to get the next sibling and next continuation *before*
+ * handling the frame: If we encounter a forced paragraph break and call
+ * ResolveParagraph within this loop, doing GetNextSibling and
+ * GetNextContinuation after that could return a bidi continuation that had
+ * just been split from the original childFrame and we would process it
+ * twice.
+ */
+ nsIFrame* nextSibling = childFrame->GetNextSibling();
+
+ // If the real frame for a placeholder is a first letter frame, we need to
+ // drill down into it and include its contents in Bidi resolution.
+ // If not, we just use the placeholder.
+ nsIFrame* frame = childFrame;
+ if (nsGkAtoms::placeholderFrame == childFrame->GetType()) {
+ nsIFrame* realFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
+ if (realFrame->GetType() == nsGkAtoms::letterFrame) {
+ frame = realFrame;
+ }
+ }
+
+ auto DifferentBidiValues = [](nsIFrame* aFrame1, nsIFrame* aFrame2) {
+ nsStyleContext* sc1 = aFrame1->StyleContext();
+ nsStyleContext* sc2 = aFrame2->StyleContext();
+ return GetBidiControl(sc1) != GetBidiControl(sc2) ||
+ GetBidiOverride(sc1) != GetBidiOverride(sc2);
+ };
+
+ nsIFrame* nextContinuation = frame->GetNextContinuation();
+ nsIFrame* prevContinuation = frame->GetPrevContinuation();
+ bool isLastFrame = !nextContinuation ||
+ DifferentBidiValues(frame, nextContinuation);
+ bool isFirstFrame = !prevContinuation ||
+ DifferentBidiValues(frame, prevContinuation);
+
+ char16_t controlChar = 0;
+ char16_t overrideChar = 0;
+ if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) {
+ if (!(frame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
+ MOZ_ASSERT(c == do_QueryFrame(frame),
+ "eBidiInlineContainer must be a nsContainerFrame subclass");
+ c->DrainSelfOverflowList();
+ }
+
+ controlChar = GetBidiControl(frame->StyleContext());
+ overrideChar = GetBidiOverride(frame->StyleContext());
+
+ // Add dummy frame pointers representing bidi control codes before
+ // the first frames of elements specifying override, isolation, or
+ // plaintext.
+ if (isFirstFrame) {
+ if (controlChar != 0) {
+ aBpd->PushBidiControl(controlChar);
+ }
+ if (overrideChar != 0) {
+ aBpd->PushBidiControl(overrideChar);
+ }
+ }
+ }
+
+ if (IsBidiLeaf(frame)) {
+ /* Bidi leaf frame: add the frame to the mLogicalFrames array,
+ * and add its index to the mContentToFrameIndex hashtable. This
+ * will be used in RemoveBidiContinuation() to identify the last
+ * frame in the array with a given content.
+ */
+ nsIContent* content = frame->GetContent();
+ aBpd->AppendFrame(frame, aLineIter, content);
+
+ // Append the content of the frame to the paragraph buffer
+ nsIAtom* frameType = frame->GetType();
+ if (nsGkAtoms::textFrame == frameType) {
+ if (content != aBpd->mPrevContent) {
+ aBpd->mPrevContent = content;
+ if (!frame->StyleText()->NewlineIsSignificant(
+ static_cast<nsTextFrame*>(frame))) {
+ content->AppendTextTo(aBpd->mBuffer);
+ } else {
+ /*
+ * For preformatted text we have to do bidi resolution on each line
+ * separately.
+ */
+ nsAutoString text;
+ content->AppendTextTo(text);
+ nsIFrame* next;
+ do {
+ next = nullptr;
+
+ int32_t start, end;
+ frame->GetOffsets(start, end);
+ int32_t endLine = text.FindChar('\n', start);
+ if (endLine == -1) {
+ /*
+ * If there is no newline in the text content, just save the
+ * text from this frame and its continuations, and do bidi
+ * resolution later
+ */
+ aBpd->AppendString(Substring(text, start));
+ while (frame && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling);
+ }
+ break;
+ }
+
+ /*
+ * If there is a newline in the frame, break the frame after the
+ * newline, do bidi resolution and repeat until the last sibling
+ */
+ ++endLine;
+
+ /*
+ * If the frame ends before the new line, save the text and move
+ * into the next continuation
+ */
+ aBpd->AppendString(Substring(text, start,
+ std::min(end, endLine) - start));
+ while (end < endLine && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling);
+ NS_ASSERTION(frame, "Premature end of continuation chain");
+ frame->GetOffsets(start, end);
+ aBpd->AppendString(Substring(text, start,
+ std::min(end, endLine) - start));
+ }
+
+ if (end < endLine) {
+ aBpd->mPrevContent = nullptr;
+ break;
+ }
+
+ bool createdContinuation = false;
+ if (uint32_t(endLine) < text.Length()) {
+ /*
+ * Timing is everything here: if the frame already has a bidi
+ * continuation, we need to make the continuation fluid *before*
+ * resetting the length of the current frame. Otherwise
+ * nsTextFrame::SetLength won't set the continuation frame's
+ * text offsets correctly.
+ *
+ * On the other hand, if the frame doesn't have a continuation,
+ * we need to create one *after* resetting the length, or
+ * CreateContinuingFrame will complain that there is no more
+ * content for the continuation.
+ */
+ next = frame->GetNextInFlow();
+ if (!next) {
+ // If the frame already has a bidi continuation, make it fluid
+ next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ JoinInlineAncestors(frame);
+ }
+ }
+
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+ textFrame->SetLength(endLine - start, nullptr);
+
+ if (!next) {
+ // If the frame has no next in flow, create one.
+ CreateContinuation(frame, &next, true);
+ createdContinuation = true;
+ }
+ // Mark the line before the newline as dirty.
+ aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty();
+ }
+ ResolveParagraphWithinBlock(aBpd);
+
+ if (!nextSibling && !createdContinuation) {
+ break;
+ } else if (next) {
+ frame = next;
+ aBpd->AppendFrame(frame, aLineIter);
+ // Mark the line after the newline as dirty.
+ aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty();
+ }
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame && frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+
+ } while (next);
+ }
+ }
+ } else if (nsGkAtoms::brFrame == frameType) {
+ // break frame -- append line separator
+ aBpd->AppendUnichar(kLineSeparator);
+ ResolveParagraphWithinBlock(aBpd);
+ } else {
+ // other frame type -- see the Unicode Bidi Algorithm:
+ // "...inline objects (such as graphics) are treated as if they are ...
+ // U+FFFC"
+ // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
+ // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
+ aBpd->AppendUnichar(content->IsHTMLElement(nsGkAtoms::wbr) ?
+ kZWSP : kObjectSubstitute);
+ if (!frame->IsInlineOutside()) {
+ // if it is not inline, end the paragraph
+ ResolveParagraphWithinBlock(aBpd);
+ }
+ }
+ } else {
+ // For a non-leaf frame, recurse into TraverseFrames
+ nsIFrame* kid = frame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(!frame->GetChildList(nsIFrame::kOverflowList).FirstChild(),
+ "should have drained the overflow list above");
+ if (kid) {
+ TraverseFrames(aLineIter, kid, aBpd);
+ }
+ }
+
+ // If the element is attributed by dir, indicate direction pop (add PDF frame)
+ if (isLastFrame) {
+ // Add a dummy frame pointer representing a bidi control code after the
+ // last frame of an element specifying embedding or override
+ if (overrideChar != 0) {
+ aBpd->PopBidiControl(overrideChar);
+ }
+ if (controlChar != 0) {
+ aBpd->PopBidiControl(controlChar);
+ }
+ }
+ childFrame = nextSibling;
+ } while (childFrame);
+
+ MOZ_ASSERT(initialLineContainer == aLineIter->GetContainer());
+}
+
+void
+nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd)
+{
+ aBpd->ClearBidiControls();
+ ResolveParagraph(aBpd);
+ aBpd->ResetData();
+}
+
+/* static */ nscoord
+nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart)
+{
+ nsSize containerSize(aContainerSize);
+
+ // If this line consists of a line frame, reorder the line frame's children.
+ if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) {
+ // The line frame is positioned at the start-edge, so use its size
+ // as the container size.
+ containerSize = aFirstFrameOnLine->GetSize();
+
+ aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
+ if (!aFirstFrameOnLine) {
+ return 0;
+ }
+ // All children of the line frame are on the first line. Setting aNumFramesOnLine
+ // to -1 makes InitLogicalArrayFromLine look at all of them.
+ aNumFramesOnLine = -1;
+ }
+
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart);
+}
+
+nsIFrame*
+nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame)
+{
+ nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
+ nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
+ firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ?
+ realFrame : firstChild;
+ }
+ return firstLeaf;
+}
+
+FrameBidiData
+nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame)
+{
+ return GetFirstLeaf(aFrame)->GetBidiData();
+}
+
+nsBidiLevel
+nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame)
+{
+ return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
+}
+
+nsBidiLevel
+nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame)
+{
+ nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
+ }
+ return firstLeaf->GetBaseLevel();
+}
+
+void
+nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
+ const nsContinuationStates* aContinuationStates,
+ bool aSpanDirMatchesLineDir,
+ bool& aIsFirst /* out */,
+ bool& aIsLast /* out */)
+{
+ /*
+ * Since we lay out frames in the line's direction, visiting a frame with
+ * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
+ * of its continuation chain frames on the line.
+ * To determine if it's the last visual frame of its continuation chain on
+ * the line or not, we count the number of frames of the chain on the line,
+ * and then reduce it when we lay out a frame of the chain. If this value
+ * becomes 1 it means that it's the last visual frame of its continuation
+ * chain on this line.
+ */
+
+ bool firstInLineOrder, lastInLineOrder;
+ nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame);
+ nsFrameContinuationState* firstFrameState;
+
+ if (!frameState->mFirstVisualFrame) {
+ // aFrame is the first visual frame of its continuation chain
+ nsFrameContinuationState* contState;
+ nsIFrame* frame;
+
+ frameState->mFrameCount = 1;
+ frameState->mFirstVisualFrame = aFrame;
+
+ /**
+ * Traverse continuation chain of aFrame in both backward and forward
+ * directions while the frames are on this line. Count the frames and
+ * set their mFirstVisualFrame to aFrame.
+ */
+ // Traverse continuation chain backward
+ for (frame = aFrame->GetPrevContinuation();
+ frame && (contState = aContinuationStates->GetEntry(frame));
+ frame = frame->GetPrevContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnPrevLines = (frame != nullptr);
+
+ // Traverse continuation chain forward
+ for (frame = aFrame->GetNextContinuation();
+ frame && (contState = aContinuationStates->GetEntry(frame));
+ frame = frame->GetNextContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnNextLines = (frame != nullptr);
+
+ firstInLineOrder = true;
+ firstFrameState = frameState;
+ } else {
+ // aFrame is not the first visual frame of its continuation chain
+ firstInLineOrder = false;
+ firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame);
+ }
+
+ lastInLineOrder = (firstFrameState->mFrameCount == 1);
+
+ if (aSpanDirMatchesLineDir) {
+ aIsFirst = firstInLineOrder;
+ aIsLast = lastInLineOrder;
+ } else {
+ aIsFirst = lastInLineOrder;
+ aIsLast = firstInLineOrder;
+ }
+
+ if (frameState->mHasContOnPrevLines) {
+ aIsFirst = false;
+ }
+ if (firstFrameState->mHasContOnNextLines) {
+ aIsLast = false;
+ }
+
+ if ((aIsFirst || aIsLast) &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // For ib splits, don't treat anything except the last part as
+ // endmost or anything except the first part as startmost.
+ // As an optimization, only get the first continuation once.
+ nsIFrame* firstContinuation = aFrame->FirstContinuation();
+ if (firstContinuation->FrameIsNonLastInIBSplit()) {
+ // We are not endmost
+ aIsLast = false;
+ }
+ if (firstContinuation->FrameIsNonFirstInIBSplit()) {
+ // We are not startmost
+ aIsFirst = false;
+ }
+ }
+
+ // Reduce number of remaining frames of the continuation chain on the line.
+ firstFrameState->mFrameCount--;
+
+ nsInlineFrame* testFrame = do_QueryFrame(aFrame);
+
+ if (testFrame) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
+
+ if (aIsFirst) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ }
+
+ if (aIsLast) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ }
+ }
+}
+
+/* static */ void
+nsBidiPresUtils::RepositionRubyContentFrame(
+ nsIFrame* aFrame, WritingMode aFrameWM, const LogicalMargin& aBorderPadding)
+{
+ const nsFrameList& childList = aFrame->PrincipalChildList();
+ if (childList.IsEmpty()) {
+ return;
+ }
+
+ // Reorder the children.
+ // XXX It currently doesn't work properly because we do not
+ // resolve frames inside ruby content frames.
+ nscoord isize = ReorderFrames(childList.FirstChild(),
+ childList.GetLength(),
+ aFrameWM, aFrame->GetSize(),
+ aBorderPadding.IStart(aFrameWM));
+ isize += aBorderPadding.IEnd(aFrameWM);
+
+ if (aFrame->StyleText()->mRubyAlign == NS_STYLE_RUBY_ALIGN_START) {
+ return;
+ }
+ nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
+ if (residualISize <= 0) {
+ return;
+ }
+
+ // When ruby-align is not "start", if the content does not fill this
+ // frame, we need to center the children.
+ const nsSize dummyContainerSize;
+ for (nsIFrame* child : childList) {
+ LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
+ rect.IStart(aFrameWM) += residualISize / 2;
+ child->SetRect(aFrameWM, rect, dummyContainerSize);
+ }
+}
+
+/* static */ nscoord
+nsBidiPresUtils::RepositionRubyFrame(
+ nsIFrame* aFrame,
+ const nsContinuationStates* aContinuationStates,
+ const WritingMode aContainerWM,
+ const LogicalMargin& aBorderPadding)
+{
+ nsIAtom* frameType = aFrame->GetType();
+ MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
+
+ nscoord icoord = 0;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ bool isLTR = frameWM.IsBidiLTR();
+ nsSize frameSize = aFrame->GetSize();
+ if (frameType == nsGkAtoms::rubyFrame) {
+ icoord += aBorderPadding.IStart(frameWM);
+ // Reposition ruby segments in a ruby container
+ for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame));
+ !e.AtEnd(); e.Next()) {
+ nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ nscoord segmentISize = RepositionFrame(rbc, isLTR, icoord,
+ aContinuationStates,
+ frameWM, false, frameSize);
+ for (nsRubyTextContainerFrame* rtc : textContainers) {
+ nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ segmentISize = std::max(segmentISize, isize);
+ }
+ icoord += segmentISize;
+ }
+ icoord += aBorderPadding.IEnd(frameWM);
+ } else if (frameType == nsGkAtoms::rubyBaseContainerFrame) {
+ // Reposition ruby columns in a ruby segment
+ auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
+ RubyColumn column;
+ e.GetColumn(column);
+
+ nscoord columnISize = RepositionFrame(column.mBaseFrame, isLTR, icoord,
+ aContinuationStates,
+ frameWM, false, frameSize);
+ for (nsRubyTextFrame* rt : column.mTextFrames) {
+ nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ columnISize = std::max(columnISize, isize);
+ }
+ icoord += columnISize;
+ }
+ } else {
+ if (frameType == nsGkAtoms::rubyBaseFrame ||
+ frameType == nsGkAtoms::rubyTextFrame) {
+ RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
+ }
+ // Note that, ruby text container is not present in all conditions
+ // above. It is intended, because the children of rtc are reordered
+ // with the children of ruby base container simultaneously. We only
+ // need to return its isize here, as it should not be changed.
+ icoord += aFrame->ISize(aContainerWM);
+ }
+ return icoord;
+}
+
+/* static */ nscoord
+nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame,
+ bool aIsEvenLevel,
+ nscoord aStartOrEnd,
+ const nsContinuationStates* aContinuationStates,
+ WritingMode aContainerWM,
+ bool aContainerReverseDir,
+ const nsSize& aContainerSize)
+{
+ nscoord lineSize = aContainerWM.IsVertical() ?
+ aContainerSize.height : aContainerSize.width;
+ NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
+ "Unconstrained inline line size in bidi frame reordering");
+ if (!aFrame)
+ return 0;
+
+ bool isFirst, isLast;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ IsFirstOrLast(aFrame,
+ aContinuationStates,
+ aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
+ isFirst /* out */,
+ isLast /* out */);
+
+ // We only need the margin if the frame is first or last in its own
+ // writing mode, but we're traversing the frames in the order of the
+ // container's writing mode. To get the right values, we set start and
+ // end margins on a logical margin in the frame's writing mode, and
+ // then convert the margin to the container's writing mode to set the
+ // coordinates.
+
+ // This method is called from nsBlockFrame::PlaceLine via the call to
+ // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
+ // have been reflowed, which is required for GetUsedMargin/Border/Padding
+ nscoord frameISize = aFrame->ISize();
+ LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
+ LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
+ // Since the visual order of frame could be different from the continuation
+ // order, we need to remove any inline border/padding [that is already applied
+ // based on continuation order] and then add it back based on the visual order
+ // (i.e. isFirst/isLast) to get the correct isize for the current frame.
+ // We don't need to do that for 'box-decoration-break:clone' because then all
+ // continuations have border/padding/margin applied.
+ if (aFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ // First remove the border/padding that was applied based on logical order.
+ if (!aFrame->GetPrevContinuation()) {
+ frameISize -= borderPadding.IStart(frameWM);
+ }
+ if (!aFrame->GetNextContinuation()) {
+ frameISize -= borderPadding.IEnd(frameWM);
+ }
+ // Set margin/border/padding based on visual order.
+ if (!isFirst) {
+ frameMargin.IStart(frameWM) = 0;
+ borderPadding.IStart(frameWM) = 0;
+ }
+ if (!isLast) {
+ frameMargin.IEnd(frameWM) = 0;
+ borderPadding.IEnd(frameWM) = 0;
+ }
+ // Add the border/padding which is now based on visual order.
+ frameISize += borderPadding.IStartEnd(frameWM);
+ }
+
+ nscoord icoord = 0;
+ if (!IsBidiLeaf(aFrame)) {
+ bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
+ icoord += reverseDir ?
+ borderPadding.IEnd(frameWM) : borderPadding.IStart(frameWM);
+ LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
+ nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
+ // Reposition the child frames
+ for (nsFrameList::Enumerator e(aFrame->PrincipalChildList());
+ !e.AtEnd(); e.Next()) {
+ icoord += RepositionFrame(e.get(), aIsEvenLevel, icoord,
+ aContinuationStates,
+ frameWM, reverseDir, frameSize);
+ }
+ icoord += reverseDir ?
+ borderPadding.IStart(frameWM) : borderPadding.IEnd(frameWM);
+ } else if (RubyUtils::IsRubyBox(aFrame->GetType())) {
+ icoord += RepositionRubyFrame(aFrame, aContinuationStates,
+ aContainerWM, borderPadding);
+ } else {
+ icoord +=
+ frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
+ }
+
+ // In the following variables, if aContainerReverseDir is true, i.e.
+ // the container is positioning its children in reverse of its logical
+ // direction, the "StartOrEnd" refers to the distance from the frame
+ // to the inline end edge of the container, elsewise, it refers to the
+ // distance to the inline start edge.
+ const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
+ nscoord marginStartOrEnd =
+ aContainerReverseDir ? margin.IEnd(aContainerWM)
+ : margin.IStart(aContainerWM);
+ nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
+
+ LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
+ rect.ISize(aContainerWM) = icoord;
+ rect.IStart(aContainerWM) =
+ aContainerReverseDir ? lineSize - frameStartOrEnd - icoord
+ : frameStartOrEnd;
+ aFrame->SetRect(aContainerWM, rect, aContainerSize);
+
+ return icoord + margin.IStartEnd(aContainerWM);
+}
+
+void
+nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates)
+{
+ nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame);
+ state->mFirstVisualFrame = nullptr;
+ state->mFrameCount = 0;
+
+ if (!IsBidiLeaf(aFrame) || RubyUtils::IsRubyBox(aFrame->GetType())) {
+ // Continue for child frames
+ for (nsIFrame* frame : aFrame->PrincipalChildList()) {
+ InitContinuationStates(frame,
+ aContinuationStates);
+ }
+ }
+}
+
+/* static */ nscoord
+nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart)
+{
+ nscoord start = aStart;
+ nsIFrame* frame;
+ int32_t count = aBld->mVisualFrames.Length();
+ int32_t index;
+ nsContinuationStates continuationStates;
+
+ // Initialize continuation states to (nullptr, 0) for
+ // each frame on the line.
+ for (index = 0; index < count; index++) {
+ InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates);
+ }
+
+ // Reposition frames in visual order
+ int32_t step, limit;
+ if (aLineWM.IsBidiLTR()) {
+ index = 0;
+ step = 1;
+ limit = count;
+ } else {
+ index = count - 1;
+ step = -1;
+ limit = -1;
+ }
+ for (; index != limit; index += step) {
+ frame = aBld->VisualFrameAt(index);
+ start += RepositionFrame(
+ frame, !(IS_LEVEL_RTL(aBld->mLevels[aBld->mIndexMap[index]])),
+ start, &continuationStates,
+ aLineWM, false, aContainerSize);
+ }
+ return start;
+}
+
+bool
+nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual)
+{
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ int32_t count = bld.FrameCount();
+
+ if (aFirstVisual) {
+ *aFirstVisual = bld.VisualFrameAt(0);
+ }
+ if (aLastVisual) {
+ *aLastVisual = bld.VisualFrameAt(count-1);
+ }
+
+ return bld.mIsReordered;
+}
+
+nsIFrame*
+nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine)
+{
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count)
+ return bld.VisualFrameAt(0);
+
+ for (int32_t i = 0; i < count - 1; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i+1);
+ }
+ }
+
+ return nullptr;
+}
+
+nsIFrame*
+nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine)
+{
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count)
+ return bld.VisualFrameAt(count-1);
+
+ for (int32_t i = 1; i < count; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i-1);
+ }
+ }
+
+ return nullptr;
+}
+
+inline nsresult
+nsBidiPresUtils::EnsureBidiContinuation(nsIFrame* aFrame,
+ nsIFrame** aNewFrame,
+ int32_t aStart,
+ int32_t aEnd)
+{
+ NS_PRECONDITION(aNewFrame, "null OUT ptr");
+ NS_PRECONDITION(aFrame, "aFrame is null");
+
+ aFrame->AdjustOffsetsForBidi(aStart, aEnd);
+ return CreateContinuation(aFrame, aNewFrame, false);
+}
+
+void
+nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData *aBpd,
+ nsIFrame* aFrame,
+ int32_t aFirstIndex,
+ int32_t aLastIndex)
+{
+ FrameBidiData bidiData = aFrame->GetBidiData();
+ bidiData.precedingControl = kBidiLevelNone;
+ for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
+ nsIFrame* frame = aBpd->FrameAt(index);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ // Make the frame and its continuation ancestors fluid,
+ // so they can be reused or deleted by normal reflow code
+ frame->Properties().Set(nsIFrame::BidiDataProperty(), bidiData);
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ while (frame) {
+ nsIFrame* prev = frame->GetPrevContinuation();
+ if (prev) {
+ MakeContinuationFluid(prev, frame);
+ frame = frame->GetParent();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ // Make sure that the last continuation we made fluid does not itself have a
+ // fluid continuation (this can happen when re-resolving after dynamic changes
+ // to content)
+ nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
+ MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
+}
+
+nsresult
+nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
+ char16_t* aText,
+ int32_t& aTextLength,
+ nsCharType aCharType)
+{
+ nsresult rv = NS_OK;
+ // ahmed
+ //adjusted for correct numeral shaping
+ uint32_t bidiOptions = aPresContext->GetBidi();
+ switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
+
+ case IBMBIDI_NUMERAL_HINDI:
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ case IBMBIDI_NUMERAL_ARABIC:
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIAN:
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
+ break;
+
+ case IBMBIDI_NUMERAL_REGULAR:
+
+ switch (aCharType) {
+
+ case eCharType_EuropeanNumber:
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case eCharType_ArabicNumber:
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_HINDICONTEXT:
+ if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
+ else if (eCharType_EuropeanNumber == aCharType)
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIANCONTEXT:
+ if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
+ else if (eCharType_EuropeanNumber == aCharType)
+ HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case IBMBIDI_NUMERAL_NOMINAL:
+ default:
+ break;
+ }
+
+ StripBidiControlCharacters(aText, aTextLength);
+ return rv;
+}
+
+void
+nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
+ int32_t& aTextLength)
+{
+ if ( (nullptr == aText) || (aTextLength < 1) ) {
+ return;
+ }
+
+ int32_t stripLen = 0;
+
+ for (int32_t i = 0; i < aTextLength; i++) {
+ // XXX: This silently ignores surrogate characters.
+ // As of Unicode 4.0, all Bidi control characters are within the BMP.
+ if (IsBidiControl((uint32_t)aText[i])) {
+ ++stripLen;
+ }
+ else {
+ aText[i - stripLen] = aText[i];
+ }
+ }
+ aTextLength -= stripLen;
+}
+
+#if 0 // XXX: for the future use ???
+void
+RemoveDiacritics(char16_t* aText,
+ int32_t& aTextLength)
+{
+ if (aText && (aTextLength > 0) ) {
+ int32_t offset = 0;
+
+ for (int32_t i = 0; i < aTextLength && aText[i]; i++) {
+ if (IS_BIDI_DIACRITIC(aText[i]) ) {
+ ++offset;
+ continue;
+ }
+ aText[i - offset] = aText[i];
+ }
+ aTextLength = i - offset;
+ aText[aTextLength] = 0;
+ }
+}
+#endif
+
+void
+nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine,
+ const char16_t* aText,
+ int32_t& aOffset,
+ int32_t aCharTypeLimit,
+ int32_t& aRunLimit,
+ int32_t& aRunLength,
+ int32_t& aRunCount,
+ uint8_t& aCharType,
+ uint8_t& aPrevCharType)
+
+{
+ bool strongTypeFound = false;
+ int32_t offset;
+ nsCharType charType;
+
+ aCharType = eCharType_OtherNeutral;
+
+ int32_t charLen;
+ for (offset = aOffset; offset < aCharTypeLimit; offset += charLen) {
+ // Make sure we give RTL chartype to all characters that would be classified
+ // as Right-To-Left by a bidi platform.
+ // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
+ charLen = 1;
+ uint32_t ch = aText[offset];
+ if (IS_HEBREW_CHAR(ch) ) {
+ charType = eCharType_RightToLeft;
+ } else if (IS_ARABIC_ALPHABETIC(ch) ) {
+ charType = eCharType_RightToLeftArabic;
+ } else {
+ if (NS_IS_HIGH_SURROGATE(ch) && offset + 1 < aCharTypeLimit &&
+ NS_IS_LOW_SURROGATE(aText[offset + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
+ charLen = 2;
+ }
+ charType = unicode::GetBidiCat(ch);
+ }
+
+ if (!CHARTYPE_IS_WEAK(charType) ) {
+
+ if (strongTypeFound
+ && (charType != aPrevCharType)
+ && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) {
+ // Stop at this point to ensure uni-directionality of the text
+ // (from platform's point of view).
+ // Also, don't mix Arabic and Hebrew content (since platform may
+ // provide BIDI support to one of them only).
+ aRunLength = offset - aOffset;
+ aRunLimit = offset;
+ ++aRunCount;
+ break;
+ }
+
+ if ( (eCharType_RightToLeftArabic == aPrevCharType
+ || eCharType_ArabicNumber == aPrevCharType)
+ && eCharType_EuropeanNumber == charType) {
+ charType = eCharType_ArabicNumber;
+ }
+
+ // Set PrevCharType to the last strong type in this frame
+ // (for correct numeric shaping)
+ aPrevCharType = charType;
+
+ strongTypeFound = true;
+ aCharType = charType;
+ }
+ }
+ aOffset = offset;
+}
+
+nsresult nsBidiPresUtils::ProcessText(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor,
+ Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount,
+ nscoord* aWidth,
+ nsBidi* aBidiEngine)
+{
+ NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments");
+
+ int32_t runCount;
+
+ nsAutoString textBuffer(aText, aLength);
+ textBuffer.ReplaceChar(kSeparators, kSpace);
+ const char16_t* text = textBuffer.get();
+
+ nsresult rv = aBidiEngine->SetPara(text, aLength, aBaseLevel);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = aBidiEngine->CountRuns(&runCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nscoord xOffset = 0;
+ nscoord width, xEndRun = 0;
+ nscoord totalWidth = 0;
+ int32_t i, start, limit, length;
+ uint32_t visualStart = 0;
+ uint8_t charType;
+ uint8_t prevType = eCharType_LeftToRight;
+
+ for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve)
+ {
+ aPosResolve[nPosResolve].visualIndex = kNotFound;
+ aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
+ aPosResolve[nPosResolve].visualWidth = kNotFound;
+ }
+
+ for (i = 0; i < runCount; i++) {
+ nsBidiDirection dir;
+ rv = aBidiEngine->GetVisualRun(i, &start, &length, &dir);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsBidiLevel level;
+ rv = aBidiEngine->GetLogicalRun(start, &limit, &level);
+ if (NS_FAILED(rv))
+ return rv;
+
+ dir = DIRECTION_FROM_LEVEL(level);
+ int32_t subRunLength = limit - start;
+ int32_t lineOffset = start;
+ int32_t typeLimit = std::min(limit, aLength);
+ int32_t subRunCount = 1;
+ int32_t subRunLimit = typeLimit;
+
+ /*
+ * If |level| is even, i.e. the direction of the run is left-to-right, we
+ * render the subruns from left to right and increment the x-coordinate
+ * |xOffset| by the width of each subrun after rendering.
+ *
+ * If |level| is odd, i.e. the direction of the run is right-to-left, we
+ * render the subruns from right to left. We begin by incrementing |xOffset| by
+ * the width of the whole run, and then decrement it by the width of each
+ * subrun before rendering. After rendering all the subruns, we restore the
+ * x-coordinate of the end of the run for the start of the next run.
+ */
+
+ if (dir == NSBIDI_RTL) {
+ aprocessor.SetText(text + start, subRunLength, dir);
+ width = aprocessor.GetWidth();
+ xOffset += width;
+ xEndRun = xOffset;
+ }
+
+ while (subRunCount > 0) {
+ // CalculateCharType can increment subRunCount if the run
+ // contains mixed character types
+ CalculateCharType(aBidiEngine, text, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType);
+
+ nsAutoString runVisualText;
+ runVisualText.Assign(text + start, subRunLength);
+ if (int32_t(runVisualText.Length()) < subRunLength)
+ return NS_ERROR_OUT_OF_MEMORY;
+ FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
+ subRunLength, (nsCharType)charType);
+
+ aprocessor.SetText(runVisualText.get(), subRunLength, dir);
+ width = aprocessor.GetWidth();
+ totalWidth += width;
+ if (dir == NSBIDI_RTL) {
+ xOffset -= width;
+ }
+ if (aMode == MODE_DRAW) {
+ aprocessor.DrawText(xOffset, width);
+ }
+
+ /*
+ * The caller may request to calculate the visual position of one
+ * or more characters.
+ */
+ for(int nPosResolve=0; nPosResolve<aPosResolveCount; ++nPosResolve)
+ {
+ nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
+ /*
+ * Did we already resolve this position's visual metric? If so, skip.
+ */
+ if (posResolve->visualLeftTwips != kNotFound)
+ continue;
+
+ /*
+ * First find out if the logical position is within this run.
+ */
+ if (start <= posResolve->logicalIndex &&
+ start + subRunLength > posResolve->logicalIndex) {
+ /*
+ * If this run is only one character long, we have an easy case:
+ * the visual position is the x-coord of the start of the run
+ * less the x-coord of the start of the whole text.
+ */
+ if (subRunLength == 1) {
+ posResolve->visualIndex = visualStart;
+ posResolve->visualLeftTwips = xOffset;
+ posResolve->visualWidth = width;
+ }
+ /*
+ * Otherwise, we need to measure the width of the run's part
+ * which is to the visual left of the index.
+ * In other words, the run is broken in two, around the logical index,
+ * and we measure the part which is visually left.
+ * If the run is right-to-left, this part will span from after the index
+ * up to the end of the run; if it is left-to-right, this part will span
+ * from the start of the run up to (and inclduing) the character before the index.
+ */
+ else {
+ /*
+ * Here is a description of how the width of the current character
+ * (posResolve->visualWidth) is calculated:
+ *
+ * LTR (current char: "P"):
+ * S A M P L E (logical index: 3, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ *
+ * RTL (current char: "M"):
+ * E L P M A S (logical index: 2, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ */
+ nscoord subWidth;
+ // The position in the text where this run's "left part" begins.
+ const char16_t* visualLeftPart;
+ const char16_t* visualRightSide;
+ if (dir == NSBIDI_RTL) {
+ // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ...
+ posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start));
+ // Skipping to the "left part".
+ visualLeftPart = text + posResolve->logicalIndex + 1;
+ // Skipping to the right side of the current character
+ visualRightSide = visualLeftPart - 1;
+ }
+ else {
+ posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start);
+ // Skipping to the "left part".
+ visualLeftPart = text + start;
+ // In LTR mode this is the same as visualLeftPart
+ visualRightSide = visualLeftPart;
+ }
+ // The delta between the start of the run and the left part's end.
+ int32_t visualLeftLength = posResolve->visualIndex - visualStart;
+ aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
+ subWidth = aprocessor.GetWidth();
+ aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
+ posResolve->visualLeftTwips = xOffset + subWidth;
+ posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
+ }
+ }
+ }
+
+ if (dir == NSBIDI_LTR) {
+ xOffset += width;
+ }
+
+ --subRunCount;
+ start = lineOffset;
+ subRunLimit = typeLimit;
+ subRunLength = typeLimit - lineOffset;
+ } // while
+ if (dir == NSBIDI_RTL) {
+ xOffset = xEndRun;
+ }
+
+ visualStart += length;
+ } // for
+
+ if (aWidth) {
+ *aWidth = totalWidth;
+ }
+ return NS_OK;
+}
+
+class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
+ : public nsBidiPresUtils::BidiProcessor
+{
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ const nsPoint& aPt)
+ : mCtx(aCtx)
+ , mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget)
+ , mFontMetrics(aFontMetrics)
+ , mPt(aPt)
+ {}
+
+ ~nsIRenderingContextBidiProcessor()
+ {
+ mFontMetrics->SetTextRunRTL(false);
+ }
+
+ virtual void SetText(const char16_t* aText,
+ int32_t aLength,
+ nsBidiDirection aDirection) override
+ {
+ mFontMetrics->SetTextRunRTL(aDirection==NSBIDI_RTL);
+ mText = aText;
+ mLength = aLength;
+ }
+
+ virtual nscoord GetWidth() override
+ {
+ return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
+ mTextRunConstructionDrawTarget);
+ }
+
+ virtual void DrawText(nscoord aIOffset,
+ nscoord) override
+ {
+ nsPoint pt(mPt);
+ if (mFontMetrics->GetVertical()) {
+ pt.y += aIOffset;
+ } else {
+ pt.x += aIOffset;
+ }
+ mFontMetrics->DrawString(mText, mLength, pt.x, pt.y,
+ mCtx, mTextRunConstructionDrawTarget);
+ }
+
+private:
+ nsRenderingContext* mCtx;
+ DrawTarget* mTextRunConstructionDrawTarget;
+ nsFontMetrics* mFontMetrics;
+ nsPoint mPt;
+ const char16_t* mText;
+ int32_t mLength;
+};
+
+nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ Mode aMode,
+ nscoord aX,
+ nscoord aY,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount,
+ nscoord* aWidth)
+{
+ nsIRenderingContextBidiProcessor processor(&aRenderingContext,
+ aTextRunConstructionDrawTarget,
+ &aFontMetrics,
+ nsPoint(aX, aY));
+ nsBidi bidiEngine;
+ return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor,
+ aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine);
+}
+
+/* static */
+nsBidiLevel
+nsBidiPresUtils::BidiLevelFromStyle(nsStyleContext* aStyleContext)
+{
+ if (aStyleContext->StyleTextReset()->mUnicodeBidi &
+ NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
+ return NSBIDI_DEFAULT_LTR;
+ }
+
+ if (aStyleContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+ return NSBIDI_RTL;
+ }
+
+ return NSBIDI_LTR;
+}
diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h
new file mode 100644
index 000000000..707df8343
--- /dev/null
+++ b/layout/base/nsBidiPresUtils.h
@@ -0,0 +1,546 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidiPresUtils_h___
+#define nsBidiPresUtils_h___
+
+#include "nsBidi.h"
+#include "nsBidiUtils.h"
+#include "nsHashKeys.h"
+#include "nsCoord.h"
+#include "nsRenderingContext.h"
+
+#ifdef DrawText
+#undef DrawText
+#endif
+
+struct BidiParagraphData;
+struct BidiLineData;
+class nsFontMetrics;
+class nsIFrame;
+class nsBlockFrame;
+class nsPresContext;
+class nsRenderingContext;
+class nsBlockInFlowLineIterator;
+class nsStyleContext;
+struct nsSize;
+template<class T> class nsTHashtable;
+namespace mozilla {
+ class WritingMode;
+ class LogicalMargin;
+} // namespace mozilla
+
+/**
+ * A structure representing some continuation state for each frame on the line,
+ * used to determine the first and the last continuation frame for each
+ * continuation chain on the line.
+ */
+struct nsFrameContinuationState : public nsVoidPtrHashKey
+{
+ explicit nsFrameContinuationState(const void *aFrame) : nsVoidPtrHashKey(aFrame) {}
+
+ /**
+ * The first visual frame in the continuation chain containing this frame, or
+ * nullptr if this frame is the first visual frame in the chain.
+ */
+ nsIFrame* mFirstVisualFrame;
+
+ /**
+ * The number of frames in the continuation chain containing this frame, if
+ * this frame is the first visual frame of the chain, or 0 otherwise.
+ */
+ uint32_t mFrameCount;
+
+ /**
+ * TRUE if this frame is the first visual frame of its continuation chain on
+ * this line and the chain has some frames on the previous lines.
+ */
+ bool mHasContOnPrevLines;
+
+ /**
+ * TRUE if this frame is the first visual frame of its continuation chain on
+ * this line and the chain has some frames left for next lines.
+ */
+ bool mHasContOnNextLines;
+};
+
+/*
+ * Following type is used to pass needed hashtable to reordering methods
+ */
+typedef nsTHashtable<nsFrameContinuationState> nsContinuationStates;
+
+/**
+ * A structure representing a logical position which should be resolved
+ * into its visual position during BiDi processing.
+ */
+struct nsBidiPositionResolve
+{
+ // [in] Logical index within string.
+ int32_t logicalIndex;
+ // [out] Visual index within string.
+ // If the logical position was not found, set to kNotFound.
+ int32_t visualIndex;
+ // [out] Visual position of the character, from the left (on the X axis), in twips.
+ // Eessentially, this is the X position (relative to the rendering context) where the text was drawn + the font metric of the visual string to the left of the given logical position.
+ // If the logical position was not found, set to kNotFound.
+ int32_t visualLeftTwips;
+ // [out] Visual width of the character, in twips.
+ // If the logical position was not found, set to kNotFound.
+ int32_t visualWidth;
+};
+
+class nsBidiPresUtils {
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ nsBidiPresUtils();
+ ~nsBidiPresUtils();
+
+ /**
+ * Interface for the processor used by ProcessText. Used by process text to
+ * collect information about the width of subruns and to notify where each
+ * subrun should be rendered.
+ */
+ class BidiProcessor {
+ public:
+ virtual ~BidiProcessor() { }
+
+ /**
+ * Sets the current text with the given length and the given direction.
+ *
+ * @remark The reason that the function gives a string instead of an index
+ * is that ProcessText copies and modifies the string passed to it, so
+ * passing an index would be impossible.
+ *
+ * @param aText The string of text.
+ * @param aLength The length of the string of text.
+ * @param aDirection The direction of the text. The string will never have
+ * mixed direction.
+ */
+ virtual void SetText(const char16_t* aText,
+ int32_t aLength,
+ nsBidiDirection aDirection) = 0;
+
+ /**
+ * Returns the measured width of the text given in SetText. If SetText was
+ * not called with valid parameters, the result of this call is undefined.
+ * This call is guaranteed to only be called once between SetText calls.
+ * Will be invoked before DrawText.
+ */
+ virtual nscoord GetWidth() = 0;
+
+ /**
+ * Draws the text given in SetText to a rendering context. If SetText was
+ * not called with valid parameters, the result of this call is undefined.
+ * This call is guaranteed to only be called once between SetText calls.
+ *
+ * @param aXOffset The offset of the left side of the substring to be drawn
+ * from the beginning of the overall string passed to ProcessText.
+ * @param aWidth The width returned by GetWidth.
+ */
+ virtual void DrawText(nscoord aXOffset,
+ nscoord aWidth) = 0;
+ };
+
+ /**
+ * Make Bidi engine calculate the embedding levels of the frames that are
+ * descendants of a given block frame.
+ *
+ * @param aBlockFrame The block frame
+ *
+ * @lina 06/18/2000
+ */
+ static nsresult Resolve(nsBlockFrame* aBlockFrame);
+ static nsresult ResolveParagraph(BidiParagraphData* aBpd);
+ static void ResolveParagraphWithinBlock(BidiParagraphData* aBpd);
+
+ /**
+ * Reorder this line using Bidi engine.
+ * Update frame array, following the new visual sequence.
+ *
+ * @return total inline size
+ *
+ * @lina 05/02/2000
+ */
+ static nscoord ReorderFrames(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ mozilla::WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart);
+
+ /**
+ * Format Unicode text, taking into account bidi capabilities
+ * of the platform. The formatting includes: reordering, Arabic shaping,
+ * symmetric and numeric swapping, removing control characters.
+ *
+ * @lina 06/18/2000
+ */
+ static nsresult FormatUnicodeText(nsPresContext* aPresContext,
+ char16_t* aText,
+ int32_t& aTextLength,
+ nsCharType aCharType);
+
+ /**
+ * Reorder plain text using the Unicode Bidi algorithm and send it to
+ * a rendering context for rendering.
+ *
+ * @param[in] aText the string to be rendered (in logical order)
+ * @param aLength the number of characters in the string
+ * @param aBaseLevel the base embedding level of the string
+ * odd values are right-to-left; even values are left-to-right, plus special
+ * constants as follows (defined in nsBidi.h)
+ * NSBIDI_LTR - left-to-right string
+ * NSBIDI_RTL - right-to-left string
+ * NSBIDI_DEFAULT_LTR - auto direction determined by first strong character,
+ * default is left-to-right
+ * NSBIDI_DEFAULT_RTL - auto direction determined by first strong character,
+ * default is right-to-left
+ *
+ * @param aPresContext the presentation context
+ * @param aRenderingContext the rendering context to render to
+ * @param aTextRunConstructionContext the rendering context to be used to construct the textrun (affects font hinting)
+ * @param aX the x-coordinate to render the string
+ * @param aY the y-coordinate to render the string
+ * @param[in,out] aPosResolve array of logical positions to resolve into visual positions; can be nullptr if this functionality is not required
+ * @param aPosResolveCount number of items in the aPosResolve array
+ */
+ static nsresult RenderText(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ nscoord aX,
+ nscoord aY,
+ nsBidiPositionResolve* aPosResolve = nullptr,
+ int32_t aPosResolveCount = 0)
+ {
+ return ProcessTextForRenderingContext(aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
+ aTextRunConstructionDrawTarget,
+ aFontMetrics,
+ MODE_DRAW, aX, aY, aPosResolve, aPosResolveCount, nullptr);
+ }
+
+ static nscoord MeasureTextWidth(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics)
+ {
+ nscoord length;
+ nsresult rv = ProcessTextForRenderingContext(aText, aLength, aBaseLevel, aPresContext,
+ aRenderingContext,
+ aRenderingContext.GetDrawTarget(),
+ aFontMetrics,
+ MODE_MEASURE, 0, 0, nullptr, 0, &length);
+ return NS_SUCCEEDED(rv) ? length : 0;
+ }
+
+ /**
+ * Check if a line is reordered, i.e., if the child frames are not
+ * all laid out left-to-right.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ * @param[out] aLeftMost : leftmost frame on this line
+ * @param[out] aRightMost : rightmost frame on this line
+ */
+ static bool CheckLineOrder(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ nsIFrame** aLeftmost,
+ nsIFrame** aRightmost);
+
+ /**
+ * Get the frame to the right of the given frame, on the same line.
+ * @param aFrame : We're looking for the frame to the right of this frame.
+ * If null, return the leftmost frame on the line.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ */
+ static nsIFrame* GetFrameToRightOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine);
+
+ /**
+ * Get the frame to the left of the given frame, on the same line.
+ * @param aFrame : We're looking for the frame to the left of this frame.
+ * If null, return the rightmost frame on the line.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ */
+ static nsIFrame* GetFrameToLeftOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine);
+
+ static nsIFrame* GetFirstLeaf(nsIFrame* aFrame);
+
+ /**
+ * Get the bidi data of the given (inline) frame.
+ */
+ static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame);
+
+ /**
+ * Get the bidi embedding level of the given (inline) frame.
+ */
+ static nsBidiLevel GetFrameEmbeddingLevel(nsIFrame* aFrame);
+
+ /**
+ * Get the bidi base level of the given (inline) frame.
+ */
+ static nsBidiLevel GetFrameBaseLevel(nsIFrame* aFrame);
+
+ /**
+ * Get an nsBidiDirection representing the direction implied by the
+ * bidi base level of the frame.
+ * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left)
+ * NSBIDI_MIXED will never be returned.
+ */
+ static nsBidiDirection ParagraphDirection(nsIFrame* aFrame) {
+ return DIRECTION_FROM_LEVEL(GetFrameBaseLevel(aFrame));
+ }
+
+ /**
+ * Get an nsBidiDirection representing the direction implied by the
+ * bidi embedding level of the frame.
+ * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left)
+ * NSBIDI_MIXED will never be returned.
+ */
+ static nsBidiDirection FrameDirection(nsIFrame* aFrame) {
+ return DIRECTION_FROM_LEVEL(GetFrameEmbeddingLevel(aFrame));
+ }
+
+ static bool IsFrameInParagraphDirection(nsIFrame* aFrame) {
+ return ParagraphDirection(aFrame) == FrameDirection(aFrame);
+ }
+
+ enum Mode { MODE_DRAW, MODE_MEASURE };
+
+ /**
+ * Reorder plain text using the Unicode Bidi algorithm and send it to
+ * a processor for rendering or measuring
+ *
+ * @param[in] aText the string to be processed (in logical order)
+ * @param aLength the number of characters in the string
+ * @param aBaseLevel the base embedding level of the string
+ * odd values are right-to-left; even values are left-to-right, plus special
+ * constants as follows (defined in nsBidi.h)
+ * NSBIDI_LTR - left-to-right string
+ * NSBIDI_RTL - right-to-left string
+ * NSBIDI_DEFAULT_LTR - auto direction determined by first strong character,
+ * default is left-to-right
+ * NSBIDI_DEFAULT_RTL - auto direction determined by first strong character,
+ * default is right-to-left
+ *
+ * @param aPresContext the presentation context
+ * @param aprocessor the bidi processor
+ * @param aMode the operation to process
+ * MODE_DRAW - invokes DrawText on the processor for each substring
+ * MODE_MEASURE - does not invoke DrawText on the processor
+ * Note that the string is always measured, regardless of mode
+ * @param[in,out] aPosResolve array of logical positions to resolve into
+ * visual positions; can be nullptr if this functionality is not required
+ * @param aPosResolveCount number of items in the aPosResolve array
+ * @param[out] aWidth Pointer to where the width will be stored (may be null)
+ */
+ static nsresult ProcessText(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor,
+ Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount,
+ nscoord* aWidth,
+ nsBidi* aBidiEngine);
+
+ /**
+ * Use style attributes to determine the base paragraph level to pass to the
+ * bidi algorithm.
+ *
+ * If |unicode-bidi| is set to "[-moz-]plaintext", returns NSBIDI_DEFAULT_LTR,
+ * in other words the direction is determined from the first strong character
+ * in the text according to rules P2 and P3 of the bidi algorithm, or LTR if
+ * there is no strong character.
+ *
+ * Otherwise returns NSBIDI_LTR or NSBIDI_RTL depending on the value of
+ * |direction|
+ */
+ static nsBidiLevel BidiLevelFromStyle(nsStyleContext* aStyleContext);
+
+private:
+ static nsresult
+ ProcessTextForRenderingContext(const char16_t* aText,
+ int32_t aLength,
+ nsBidiLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ Mode aMode,
+ nscoord aX, // DRAW only
+ nscoord aY, // DRAW only
+ nsBidiPositionResolve* aPosResolve, /* may be null */
+ int32_t aPosResolveCount,
+ nscoord* aWidth /* may be null */);
+
+ /**
+ * Traverse the child frames of the block element and:
+ * Set up an array of the frames in logical order
+ * Create a string containing the text content of all the frames
+ * If we encounter content that requires us to split the element into more
+ * than one paragraph for bidi resolution, resolve the paragraph up to that
+ * point.
+ */
+ static void TraverseFrames(nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame* aCurrentFrame,
+ BidiParagraphData* aBpd);
+
+ /**
+ * Position ruby content frames (ruby base/text frame).
+ * Called from RepositionRubyFrame.
+ */
+ static void RepositionRubyContentFrame(
+ nsIFrame* aFrame, mozilla::WritingMode aFrameWM,
+ const mozilla::LogicalMargin& aBorderPadding);
+
+ /*
+ * Position ruby frames. Called from RepositionFrame.
+ */
+ static nscoord RepositionRubyFrame(
+ nsIFrame* aFrame,
+ const nsContinuationStates* aContinuationStates,
+ const mozilla::WritingMode aContainerWM,
+ const mozilla::LogicalMargin& aBorderPadding);
+
+ /*
+ * Position aFrame and its descendants to their visual places. Also if aFrame
+ * is not leaf, resize it to embrace its children.
+ *
+ * @param aFrame The frame which itself and its children are
+ * going to be repositioned
+ * @param aIsEvenLevel TRUE means the embedding level of this frame
+ * is even (LTR)
+ * @param aStartOrEnd The distance to the start or the end of aFrame
+ * without considering its inline margin. If the
+ * container is reordering frames in reverse
+ * direction, it's the distance to the end,
+ * otherwise, it's the distance to the start.
+ * @param aContinuationStates A map from nsIFrame* to nsFrameContinuationState
+ * @return The isize aFrame takes, including margins.
+ */
+ static nscoord RepositionFrame(nsIFrame* aFrame,
+ bool aIsEvenLevel,
+ nscoord aStartOrEnd,
+ const nsContinuationStates* aContinuationStates,
+ mozilla::WritingMode aContainerWM,
+ bool aContainerReverseOrder,
+ const nsSize& aContainerSize);
+
+ /*
+ * Initialize the continuation state(nsFrameContinuationState) to
+ * (nullptr, 0) for aFrame and its descendants.
+ *
+ * @param aFrame The frame which itself and its descendants will
+ * be initialized
+ * @param aContinuationStates A map from nsIFrame* to nsFrameContinuationState
+ */
+ static void InitContinuationStates(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates);
+
+ /*
+ * Determine if aFrame is first or last, and set aIsFirst and
+ * aIsLast values. Also set continuation states of
+ * aContinuationStates.
+ *
+ * A frame is first if it's the first appearance of its continuation
+ * chain on the line and the chain is on its first line.
+ * A frame is last if it's the last appearance of its continuation
+ * chain on the line and the chain is on its last line.
+ *
+ * N.B: "First appearance" and "Last appearance" in the previous
+ * paragraph refer to the frame's inline direction, not necessarily
+ * the line's.
+ *
+ * @param aContinuationStates A map from nsIFrame* to
+ * nsFrameContinuationState
+ * @param[in] aSpanDirMatchesLineDir TRUE means that the inline
+ * direction of aFrame is the same
+ * as its container
+ * @param[out] aIsFirst TRUE means aFrame is first frame
+ * or continuation
+ * @param[out] aIsLast TRUE means aFrame is last frame
+ * or continuation
+ */
+ static void IsFirstOrLast(nsIFrame* aFrame,
+ const nsContinuationStates* aContinuationStates,
+ bool aSpanInLineOrder /* in */,
+ bool& aIsFirst /* out */,
+ bool& aIsLast /* out */);
+
+ /**
+ * Adjust frame positions following their visual order
+ *
+ * @param aFirstChild the first kid
+ * @return total inline size
+ *
+ * @lina 04/11/2000
+ */
+ static nscoord RepositionInlineFrames(BidiLineData* aBld,
+ mozilla::WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart);
+
+ /**
+ * Helper method for Resolve()
+ * Truncate a text frame to the end of a single-directional run and possibly
+ * create a continuation frame for the remainder of its content.
+ *
+ * @param aFrame the original frame
+ * @param aNewFrame [OUT] the new frame that was created
+ * @param aStart [IN] the start of the content mapped by aFrame (and
+ * any fluid continuations)
+ * @param aEnd [IN] the offset of the end of the single-directional
+ * text run.
+ * @see Resolve()
+ * @see RemoveBidiContinuation()
+ */
+ static inline
+ nsresult EnsureBidiContinuation(nsIFrame* aFrame,
+ nsIFrame** aNewFrame,
+ int32_t aStart,
+ int32_t aEnd);
+
+ /**
+ * Helper method for Resolve()
+ * Convert one or more bidi continuation frames created in a previous reflow by
+ * EnsureBidiContinuation() into fluid continuations.
+ * @param aFrame the frame whose continuations are to be removed
+ * @param aFirstIndex index of aFrame in mLogicalFrames
+ * @param aLastIndex index of the last frame to be removed
+ *
+ * @see Resolve()
+ * @see EnsureBidiContinuation()
+ */
+ static void RemoveBidiContinuation(BidiParagraphData* aBpd,
+ nsIFrame* aFrame,
+ int32_t aFirstIndex,
+ int32_t aLastIndex);
+ static void CalculateCharType(nsBidi* aBidiEngine,
+ const char16_t* aText,
+ int32_t& aOffset,
+ int32_t aCharTypeLimit,
+ int32_t& aRunLimit,
+ int32_t& aRunLength,
+ int32_t& aRunCount,
+ uint8_t& aCharType,
+ uint8_t& aPrevCharType);
+
+ static void StripBidiControlCharacters(char16_t* aText,
+ int32_t& aTextLength);
+};
+
+#endif /* nsBidiPresUtils_h___ */
diff --git a/layout/base/nsBidi_ICU.cpp b/layout/base/nsBidi_ICU.cpp
new file mode 100644
index 000000000..482668e24
--- /dev/null
+++ b/layout/base/nsBidi_ICU.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBidi_ICU.h"
+#include "ICUUtils.h"
+
+nsBidi::nsBidi()
+{
+ mBiDi = ubidi_open();
+}
+
+nsBidi::~nsBidi()
+{
+ ubidi_close(mBiDi);
+}
+
+nsresult nsBidi::SetPara(const char16_t *aText, int32_t aLength,
+ nsBidiLevel aParaLevel)
+{
+ UErrorCode error = U_ZERO_ERROR;
+ ubidi_setPara(mBiDi, reinterpret_cast<const UChar*>(aText), aLength,
+ aParaLevel, nullptr, &error);
+ return ICUUtils::UErrorToNsResult(error);
+}
+
+nsresult nsBidi::GetDirection(nsBidiDirection* aDirection)
+{
+ *aDirection = nsBidiDirection(ubidi_getDirection(mBiDi));
+ return NS_OK;
+}
+
+nsresult nsBidi::GetParaLevel(nsBidiLevel* aParaLevel)
+{
+ *aParaLevel = ubidi_getParaLevel(mBiDi);
+ return NS_OK;
+}
+
+nsresult nsBidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
+ nsBidiLevel* aLevel)
+{
+ ubidi_getLogicalRun(mBiDi, aLogicalStart, aLogicalLimit, aLevel);
+ return NS_OK;
+}
+
+nsresult nsBidi::CountRuns(int32_t* aRunCount)
+{
+ UErrorCode errorCode = U_ZERO_ERROR;
+ *aRunCount = ubidi_countRuns(mBiDi, &errorCode);
+ return ICUUtils::UErrorToNsResult(errorCode);
+}
+
+nsresult nsBidi::GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
+ int32_t* aLength, nsBidiDirection* aDirection)
+{
+ *aDirection = nsBidiDirection(ubidi_getVisualRun(mBiDi, aRunIndex,
+ aLogicalStart, aLength));
+ return NS_OK;
+}
+
+nsresult nsBidi::ReorderVisual(const nsBidiLevel* aLevels, int32_t aLength,
+ int32_t* aIndexMap)
+{
+ ubidi_reorderVisual(aLevels, aLength, aIndexMap);
+ return NS_OK;
+}
diff --git a/layout/base/nsBidi_ICU.h b/layout/base/nsBidi_ICU.h
new file mode 100644
index 000000000..3ead6e2bb
--- /dev/null
+++ b/layout/base/nsBidi_ICU.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidi_ICU_h__
+#define nsBidi_ICU_h__
+
+#include "unicode/ubidi.h"
+#include "nsIFrame.h" // for nsBidiLevel/nsBidiDirection declarations
+
+// nsBidi implemented as a simple wrapper around the bidi reordering engine
+// from ICU.
+// We could eliminate this and let callers use the ICU functions directly
+// once we no longer care about building without ICU available.
+
+class nsBidi
+{
+public:
+ /** @brief Default constructor.
+ *
+ * The nsBidi object is initially empty. It is assigned
+ * the Bidi properties of a paragraph by <code>SetPara()</code>.
+ */
+ explicit nsBidi();
+
+ /** @brief Destructor. */
+ virtual ~nsBidi();
+
+
+ /**
+ * Perform the Unicode Bidi algorithm.
+ *
+ * @param aText is a pointer to the single-paragraph text that the
+ * Bidi algorithm will be performed on
+ * (step (P1) of the algorithm is performed externally).
+ * <strong>The text must be (at least) <code>aLength</code> long.</strong>
+ *
+ * @param aLength is the length of the text; if <code>aLength==-1</code> then
+ * the text must be zero-terminated.
+ *
+ * @param aParaLevel specifies the default level for the paragraph;
+ * it is typically 0 (LTR) or 1 (RTL).
+ * If the function shall determine the paragraph level from the text,
+ * then <code>aParaLevel</code> can be set to
+ * either <code>NSBIDI_DEFAULT_LTR</code>
+ * or <code>NSBIDI_DEFAULT_RTL</code>;
+ * if there is no strongly typed character, then
+ * the desired default is used (0 for LTR or 1 for RTL).
+ * Any other value between 0 and <code>NSBIDI_MAX_EXPLICIT_LEVEL</code>
+ * is also valid, with odd levels indicating RTL.
+ */
+ nsresult SetPara(const char16_t *aText, int32_t aLength,
+ nsBidiLevel aParaLevel);
+
+ /**
+ * Get the directionality of the text.
+ *
+ * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates
+ * if the entire text represented by this object is unidirectional,
+ * and which direction, or if it is mixed-directional.
+ *
+ * @see nsBidiDirection
+ */
+ nsresult GetDirection(nsBidiDirection* aDirection);
+
+ /**
+ * Get the paragraph level of the text.
+ *
+ * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating
+ * the paragraph level
+ *
+ * @see nsBidiLevel
+ */
+ nsresult GetParaLevel(nsBidiLevel* aParaLevel);
+
+ /**
+ * Get a logical run.
+ * This function returns information about a run and is used
+ * to retrieve runs in logical order.<p>
+ * This is especially useful for line-breaking on a paragraph.
+ *
+ * @param aLogicalStart is the first character of the run.
+ *
+ * @param aLogicalLimit will receive the limit of the run.
+ * The l-value that you point to here may be the
+ * same expression (variable) as the one for
+ * <code>aLogicalStart</code>.
+ * This pointer can be <code>nullptr</code> if this
+ * value is not necessary.
+ *
+ * @param aLevel will receive the level of the run.
+ * This pointer can be <code>nullptr</code> if this
+ * value is not necessary.
+ */
+ nsresult GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
+ nsBidiLevel* aLevel);
+
+ /**
+ * Get the number of runs.
+ * This function may invoke the actual reordering on the
+ * <code>nsBidi</code> object, after <code>SetPara</code>
+ * may have resolved only the levels of the text. Therefore,
+ * <code>CountRuns</code> may have to allocate memory,
+ * and may fail doing so.
+ *
+ * @param aRunCount will receive the number of runs.
+ */
+ nsresult CountRuns(int32_t* aRunCount);
+
+ /**
+ * Get one run's logical start, length, and directionality,
+ * which can be 0 for LTR or 1 for RTL.
+ * In an RTL run, the character at the logical start is
+ * visually on the right of the displayed run.
+ * The length is the number of characters in the run.<p>
+ * <code>CountRuns</code> should be called
+ * before the runs are retrieved.
+ *
+ * @param aRunIndex is the number of the run in visual order, in the
+ * range <code>[0..CountRuns-1]</code>.
+ *
+ * @param aLogicalStart is the first logical character index in the text.
+ * The pointer may be <code>nullptr</code> if this index is not needed.
+ *
+ * @param aLength is the number of characters (at least one) in the run.
+ * The pointer may be <code>nullptr</code> if this is not needed.
+ *
+ * @param aDirection will receive the directionality of the run,
+ * <code>NSBIDI_LTR==0</code> or <code>NSBIDI_RTL==1</code>,
+ * never <code>NSBIDI_MIXED</code>.
+ *
+ * @see CountRuns<p>
+ *
+ * Example:
+ * @code
+ * int32_t i, count, logicalStart, visualIndex=0, length;
+ * nsBidiDirection dir;
+ * pBidi->CountRuns(&count);
+ * for(i=0; i<count; ++i) {
+ * pBidi->GetVisualRun(i, &logicalStart, &length, &dir);
+ * if(NSBIDI_LTR==dir) {
+ * do { // LTR
+ * show_char(text[logicalStart++], visualIndex++);
+ * } while(--length>0);
+ * } else {
+ * logicalStart+=length; // logicalLimit
+ * do { // RTL
+ * show_char(text[--logicalStart], visualIndex++);
+ * } while(--length>0);
+ * }
+ * }
+ * @endcode
+ *
+ * Note that in right-to-left runs, code like this places
+ * modifier letters before base characters and second surrogates
+ * before first ones.
+ */
+ nsresult GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
+ int32_t* aLength, nsBidiDirection* aDirection);
+
+ /**
+ * This is a convenience function that does not use a nsBidi object.
+ * It is intended to be used for when an application has determined the levels
+ * of objects (character sequences) and just needs to have them reordered (L2).
+ * This is equivalent to using <code>GetVisualMap</code> on a
+ * <code>nsBidi</code> object.
+ *
+ * @param aLevels is an array with <code>aLength</code> levels that have been
+ * determined by the application.
+ *
+ * @param aLength is the number of levels in the array, or, semantically,
+ * the number of objects to be reordered.
+ * It must be <code>aLength>0</code>.
+ *
+ * @param aIndexMap is a pointer to an array of <code>aLength</code>
+ * indexes which will reflect the reordering of the characters.
+ * The array does not need to be initialized.<p>
+ * The index map will result in
+ * <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>.
+ */
+ static nsresult ReorderVisual(const nsBidiLevel* aLevels, int32_t aLength,
+ int32_t* aIndexMap);
+
+protected:
+ UBiDi* mBiDi;
+};
+
+#endif // _nsBidi_ICU_h_
diff --git a/layout/base/nsBidi_noICU.cpp b/layout/base/nsBidi_noICU.cpp
new file mode 100644
index 000000000..0b9c58e55
--- /dev/null
+++ b/layout/base/nsBidi_noICU.cpp
@@ -0,0 +1,2089 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBidi.h"
+#include "nsUnicodeProperties.h"
+#include "nsCRTGlue.h"
+
+using namespace mozilla::unicode;
+
+static_assert(mozilla::kBidiLevelNone > NSBIDI_MAX_EXPLICIT_LEVEL + 1,
+ "The pseudo embedding level should be out-of-range");
+
+// These are #defined in <sys/regset.h> under Solaris 10 x86
+#undef CS
+#undef ES
+
+/* Comparing the description of the Bidi algorithm with this implementation
+ is easier with the same names for the Bidi types in the code as there.
+*/
+enum {
+ L = eCharType_LeftToRight,
+ R = eCharType_RightToLeft,
+ EN = eCharType_EuropeanNumber,
+ ES = eCharType_EuropeanNumberSeparator,
+ ET = eCharType_EuropeanNumberTerminator,
+ AN = eCharType_ArabicNumber,
+ CS = eCharType_CommonNumberSeparator,
+ B = eCharType_BlockSeparator,
+ S = eCharType_SegmentSeparator,
+ WS = eCharType_WhiteSpaceNeutral,
+ O_N = eCharType_OtherNeutral,
+ LRE = eCharType_LeftToRightEmbedding,
+ LRO = eCharType_LeftToRightOverride,
+ AL = eCharType_RightToLeftArabic,
+ RLE = eCharType_RightToLeftEmbedding,
+ RLO = eCharType_RightToLeftOverride,
+ PDF = eCharType_PopDirectionalFormat,
+ NSM = eCharType_DirNonSpacingMark,
+ BN = eCharType_BoundaryNeutral,
+ LRI = eCharType_LeftToRightIsolate,
+ RLI = eCharType_RightToLeftIsolate,
+ FSI = eCharType_FirstStrongIsolate,
+ PDI = eCharType_PopDirectionalIsolate,
+ ENL, /* EN after W7 */ /* 23 */
+ ENR, /* EN not subject to W7 */ /* 24 */
+ dirPropCount
+};
+
+#define IS_STRONG_TYPE(dirProp) ((dirProp) <= R || (dirProp) == AL)
+
+/* to avoid some conditional statements, use tiny constant arrays */
+static Flags flagLR[2]={ DIRPROP_FLAG(L), DIRPROP_FLAG(R) };
+static Flags flagE[2]={ DIRPROP_FLAG(LRE), DIRPROP_FLAG(RLE) };
+static Flags flagO[2]={ DIRPROP_FLAG(LRO), DIRPROP_FLAG(RLO) };
+
+#define DIRPROP_FLAG_LR(level) flagLR[(level)&1]
+#define DIRPROP_FLAG_E(level) flagE[(level)&1]
+#define DIRPROP_FLAG_O(level) flagO[(level)&1]
+
+#define NO_OVERRIDE(level) ((level)&~NSBIDI_LEVEL_OVERRIDE)
+
+static inline uint8_t
+DirFromStrong(uint8_t aDirProp)
+{
+ MOZ_ASSERT(IS_STRONG_TYPE(aDirProp));
+ return aDirProp == L ? L : R;
+}
+
+/*
+ * General implementation notes:
+ *
+ * Throughout the implementation, there are comments like (W2) that refer to
+ * rules of the Bidi algorithm in its version 5, in this example to the second
+ * rule of the resolution of weak types.
+ *
+ * For handling surrogate pairs, where two UChar's form one "abstract" (or UTF-32)
+ * character according to UTF-16, the second UChar gets the directional property of
+ * the entire character assigned, while the first one gets a BN, a boundary
+ * neutral, type, which is ignored by most of the algorithm according to
+ * rule (X9) and the implementation suggestions of the Bidi algorithm.
+ *
+ * Later, AdjustWSLevels() will set the level for each BN to that of the
+ * following character (UChar), which results in surrogate pairs getting the
+ * same level on each of their surrogates.
+ *
+ * In a UTF-8 implementation, the same thing could be done: the last byte of
+ * a multi-byte sequence would get the "real" property, while all previous
+ * bytes of that sequence would get BN.
+ *
+ * It is not possible to assign all those parts of a character the same real
+ * property because this would fail in the resolution of weak types with rules
+ * that look at immediately surrounding types.
+ *
+ * As a related topic, this implementation does not remove Boundary Neutral
+ * types from the input, but ignores them whenever this is relevant.
+ * For example, the loop for the resolution of the weak types reads
+ * types until it finds a non-BN.
+ * Also, explicit embedding codes are neither changed into BN nor removed.
+ * They are only treated the same way real BNs are.
+ * As stated before, AdjustWSLevels() takes care of them at the end.
+ * For the purpose of conformance, the levels of all these codes
+ * do not matter.
+ *
+ * Note that this implementation never modifies the dirProps
+ * after the initial setup, except for FSI which is changed to either
+ * LRI or RLI in GetDirProps(), and paired brackets which may be changed
+ * to L or R according to N0.
+ *
+ *
+ * In this implementation, the resolution of weak types (Wn),
+ * neutrals (Nn), and the assignment of the resolved level (In)
+ * are all done in one single loop, in ResolveImplicitLevels().
+ * Changes of dirProp values are done on the fly, without writing
+ * them back to the dirProps array.
+ *
+ *
+ * This implementation contains code that allows to bypass steps of the
+ * algorithm that are not needed on the specific paragraph
+ * in order to speed up the most common cases considerably,
+ * like text that is entirely LTR, or RTL text without numbers.
+ *
+ * Most of this is done by setting a bit for each directional property
+ * in a flags variable and later checking for whether there are
+ * any LTR characters or any RTL characters, or both, whether
+ * there are any explicit embedding codes, etc.
+ *
+ * If the (Xn) steps are performed, then the flags are re-evaluated,
+ * because they will then not contain the embedding codes any more
+ * and will be adjusted for override codes, so that subsequently
+ * more bypassing may be possible than what the initial flags suggested.
+ *
+ * If the text is not mixed-directional, then the
+ * algorithm steps for the weak type resolution are not performed,
+ * and all levels are set to the paragraph level.
+ *
+ * If there are no explicit embedding codes, then the (Xn) steps
+ * are not performed.
+ *
+ * If embedding levels are supplied as a parameter, then all
+ * explicit embedding codes are ignored, and the (Xn) steps
+ * are not performed.
+ *
+ * White Space types could get the level of the run they belong to,
+ * and are checked with a test of (flags&MASK_EMBEDDING) to
+ * consider if the paragraph direction should be considered in
+ * the flags variable.
+ *
+ * If there are no White Space types in the paragraph, then
+ * (L1) is not necessary in AdjustWSLevels().
+ */
+nsBidi::nsBidi()
+{
+ Init();
+}
+
+nsBidi::~nsBidi()
+{
+ Free();
+}
+
+void nsBidi::Init()
+{
+ /* reset the object, all pointers nullptr, all flags false, all sizes 0 */
+ mLength = 0;
+ mParaLevel = 0;
+ mFlags = 0;
+ mDirection = NSBIDI_LTR;
+ mTrailingWSStart = 0;
+
+ mDirPropsSize = 0;
+ mLevelsSize = 0;
+ mRunsSize = 0;
+ mIsolatesSize = 0;
+
+ mRunCount = -1;
+ mIsolateCount = -1;
+
+ mDirProps=nullptr;
+ mLevels=nullptr;
+ mRuns=nullptr;
+ mIsolates=nullptr;
+
+ mDirPropsMemory=nullptr;
+ mLevelsMemory=nullptr;
+ mRunsMemory=nullptr;
+ mIsolatesMemory=nullptr;
+}
+
+/*
+ * We are allowed to allocate memory if aMemory==nullptr
+ * for each array that we need.
+ * We also try to grow and shrink memory as needed if we
+ * allocate it.
+ *
+ * Assume aSizeNeeded>0.
+ * If *aMemory!=nullptr, then assume *aSize>0.
+ *
+ * ### this realloc() may unnecessarily copy the old data,
+ * which we know we don't need any more;
+ * is this the best way to do this??
+ */
+/*static*/
+bool
+nsBidi::GetMemory(void **aMemory, size_t *aSize, size_t aSizeNeeded)
+{
+ /* check for existing memory */
+ if(*aMemory==nullptr) {
+ /* we need to allocate memory */
+ *aMemory=malloc(aSizeNeeded);
+ if (*aMemory!=nullptr) {
+ *aSize=aSizeNeeded;
+ return true;
+ } else {
+ *aSize=0;
+ return false;
+ }
+ } else {
+ /* there is some memory, is it enough or too much? */
+ if(aSizeNeeded!=*aSize) {
+ /* we may try to grow or shrink */
+ void *memory=realloc(*aMemory, aSizeNeeded);
+
+ if(memory!=nullptr) {
+ *aMemory=memory;
+ *aSize=aSizeNeeded;
+ return true;
+ } else {
+ /* we failed to grow */
+ return false;
+ }
+ } else {
+ /* we have at least enough memory and must not allocate */
+ return true;
+ }
+ }
+}
+
+void nsBidi::Free()
+{
+ free(mDirPropsMemory);
+ mDirPropsMemory = nullptr;
+ free(mLevelsMemory);
+ mLevelsMemory = nullptr;
+ free(mRunsMemory);
+ mRunsMemory = nullptr;
+ free(mIsolatesMemory);
+ mIsolatesMemory = nullptr;
+}
+
+/* SetPara ------------------------------------------------------------ */
+
+nsresult nsBidi::SetPara(const char16_t *aText, int32_t aLength,
+ nsBidiLevel aParaLevel)
+{
+ nsBidiDirection direction;
+
+ /* check the argument values */
+ if(aText==nullptr ||
+ ((NSBIDI_MAX_EXPLICIT_LEVEL<aParaLevel) && !IS_DEFAULT_LEVEL(aParaLevel)) ||
+ aLength<-1
+ ) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if(aLength==-1) {
+ aLength = NS_strlen(aText);
+ }
+
+ /* initialize member data */
+ mLength = aLength;
+ mParaLevel=aParaLevel;
+ mDirection=aParaLevel & 1 ? NSBIDI_RTL : NSBIDI_LTR;
+ mTrailingWSStart=aLength; /* the levels[] will reflect the WS run */
+
+ mDirProps=nullptr;
+ mLevels=nullptr;
+ mRuns=nullptr;
+
+ if(aLength==0) {
+ /*
+ * For an empty paragraph, create an nsBidi object with the aParaLevel and
+ * the flags and the direction set but without allocating zero-length arrays.
+ * There is nothing more to do.
+ */
+ if(IS_DEFAULT_LEVEL(aParaLevel)) {
+ mParaLevel&=1;
+ }
+ mFlags=DIRPROP_FLAG_LR(aParaLevel);
+ mRunCount=0;
+ return NS_OK;
+ }
+
+ mRunCount=-1;
+
+ /*
+ * Get the directional properties,
+ * the flags bit-set, and
+ * determine the partagraph level if necessary.
+ */
+ if(GETDIRPROPSMEMORY(aLength)) {
+ mDirProps=mDirPropsMemory;
+ GetDirProps(aText);
+ } else {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* determine explicit levels according to the (Xn) rules */
+ if(GETLEVELSMEMORY(aLength)) {
+ mLevels=mLevelsMemory;
+ ResolveExplicitLevels(&direction, aText);
+ } else {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* allocate isolate memory */
+ if (mIsolateCount <= SIMPLE_ISOLATES_SIZE) {
+ mIsolates = mSimpleIsolates;
+ } else {
+ if (mIsolateCount * sizeof(Isolate) <= mIsolatesSize) {
+ mIsolates = mIsolatesMemory;
+ } else {
+ if (GETISOLATESMEMORY(mIsolateCount)) {
+ mIsolates = mIsolatesMemory;
+ } else {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ mIsolateCount = -1; /* current isolates stack entry == none */
+
+ /*
+ * The steps after (X9) in the Bidi algorithm are performed only if
+ * the paragraph text has mixed directionality!
+ */
+ mDirection = direction;
+ switch(direction) {
+ case NSBIDI_LTR:
+ /* make sure paraLevel is even */
+ mParaLevel=(mParaLevel+1)&~1;
+
+ /* all levels are implicitly at paraLevel (important for GetLevels()) */
+ mTrailingWSStart=0;
+ break;
+ case NSBIDI_RTL:
+ /* make sure paraLevel is odd */
+ mParaLevel|=1;
+
+ /* all levels are implicitly at paraLevel (important for GetLevels()) */
+ mTrailingWSStart=0;
+ break;
+ default:
+ /*
+ * If there are no external levels specified and there
+ * are no significant explicit level codes in the text,
+ * then we can treat the entire paragraph as one run.
+ * Otherwise, we need to perform the following rules on runs of
+ * the text with the same embedding levels. (X10)
+ * "Significant" explicit level codes are ones that actually
+ * affect non-BN characters.
+ * Examples for "insignificant" ones are empty embeddings
+ * LRE-PDF, LRE-RLE-PDF-PDF, etc.
+ */
+ if(!(mFlags&DIRPROP_FLAG_MULTI_RUNS)) {
+ ResolveImplicitLevels(0, aLength,
+ GET_LR_FROM_LEVEL(mParaLevel),
+ GET_LR_FROM_LEVEL(mParaLevel));
+ } else {
+ /* sor, eor: start and end types of same-level-run */
+ nsBidiLevel *levels=mLevels;
+ int32_t start, limit=0;
+ nsBidiLevel level, nextLevel;
+ DirProp sor, eor;
+
+ /* determine the first sor and set eor to it because of the loop body (sor=eor there) */
+ level=mParaLevel;
+ nextLevel=levels[0];
+ if(level<nextLevel) {
+ eor=GET_LR_FROM_LEVEL(nextLevel);
+ } else {
+ eor=GET_LR_FROM_LEVEL(level);
+ }
+
+ do {
+ /* determine start and limit of the run (end points just behind the run) */
+
+ /* the values for this run's start are the same as for the previous run's end */
+ sor=eor;
+ start=limit;
+ level=nextLevel;
+
+ /* search for the limit of this run */
+ while(++limit<aLength &&
+ (levels[limit]==level ||
+ (DIRPROP_FLAG(mDirProps[limit])&MASK_BN_EXPLICIT))) {}
+
+ /* get the correct level of the next run */
+ if(limit<aLength) {
+ nextLevel=levels[limit];
+ } else {
+ nextLevel=mParaLevel;
+ }
+
+ /* determine eor from max(level, nextLevel); sor is last run's eor */
+ if((level&~NSBIDI_LEVEL_OVERRIDE)<(nextLevel&~NSBIDI_LEVEL_OVERRIDE)) {
+ eor=GET_LR_FROM_LEVEL(nextLevel);
+ } else {
+ eor=GET_LR_FROM_LEVEL(level);
+ }
+
+ /* if the run consists of overridden directional types, then there
+ are no implicit types to be resolved */
+ if(!(level&NSBIDI_LEVEL_OVERRIDE)) {
+ ResolveImplicitLevels(start, limit, sor, eor);
+ } else {
+ do {
+ levels[start++] &= ~NSBIDI_LEVEL_OVERRIDE;
+ } while (start < limit);
+ }
+ } while(limit<aLength);
+ }
+
+ /* reset the embedding levels for some non-graphic characters (L1), (X9) */
+ AdjustWSLevels();
+ break;
+ }
+
+ return NS_OK;
+}
+
+/* perform (P2)..(P3) ------------------------------------------------------- */
+
+/*
+ * Get the directional properties for the text,
+ * calculate the flags bit-set, and
+ * determine the partagraph level if necessary.
+ */
+void nsBidi::GetDirProps(const char16_t *aText)
+{
+ DirProp *dirProps=mDirPropsMemory; /* mDirProps is const */
+
+ int32_t i=0, length=mLength;
+ Flags flags=0; /* collect all directionalities in the text */
+ char16_t uchar;
+ DirProp dirProp;
+
+ bool isDefaultLevel = IS_DEFAULT_LEVEL(mParaLevel);
+
+ enum State {
+ NOT_SEEKING_STRONG, /* 0: not after FSI */
+ SEEKING_STRONG_FOR_PARA, /* 1: looking for first strong char in para */
+ SEEKING_STRONG_FOR_FSI, /* 2: looking for first strong after FSI */
+ LOOKING_FOR_PDI /* 3: found strong after FSI, looking for PDI */
+ };
+ State state;
+
+ /* The following stacks are used to manage isolate sequences. Those
+ sequences may be nested, but obviously never more deeply than the
+ maximum explicit embedding level.
+ lastStack is the index of the last used entry in the stack. A value of -1
+ means that there is no open isolate sequence. */
+ /* The following stack contains the position of the initiator of
+ each open isolate sequence */
+ int32_t isolateStartStack[NSBIDI_MAX_EXPLICIT_LEVEL + 1];
+ /* The following stack contains the last known state before
+ encountering the initiator of an isolate sequence */
+ State previousStateStack[NSBIDI_MAX_EXPLICIT_LEVEL + 1];
+ int32_t stackLast = -1;
+
+ if(isDefaultLevel) {
+ /*
+ * see comment in nsBidi.h:
+ * the DEFAULT_XXX values are designed so that
+ * their bit 0 alone yields the intended default
+ */
+ mParaLevel &= 1;
+ state = SEEKING_STRONG_FOR_PARA;
+ } else {
+ state = NOT_SEEKING_STRONG;
+ }
+
+ /* determine the paragraph level (P2..P3) */
+ for(/* i = 0 above */; i < length;) {
+ uchar=aText[i];
+ if(!IS_FIRST_SURROGATE(uchar) || i+1==length || !IS_SECOND_SURROGATE(aText[i+1])) {
+ /* not a surrogate pair */
+ flags|=DIRPROP_FLAG(dirProps[i]=dirProp=GetBidiCat((uint32_t)uchar));
+ } else {
+ /* a surrogate pair */
+ dirProps[i++]=BN; /* first surrogate in the pair gets the BN type */
+ flags|=DIRPROP_FLAG(dirProps[i]=dirProp=GetBidiCat(GET_UTF_32(uchar, aText[i])))|DIRPROP_FLAG(BN);
+ }
+ ++i;
+
+ switch (dirProp) {
+ case L:
+ if (state == SEEKING_STRONG_FOR_PARA) {
+ mParaLevel = 0;
+ state = NOT_SEEKING_STRONG;
+ } else if (state == SEEKING_STRONG_FOR_FSI) {
+ if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
+ dirProps[isolateStartStack[stackLast]] = LRI;
+ flags |= DIRPROP_FLAG(LRI);
+ }
+ state = LOOKING_FOR_PDI;
+ }
+ break;
+
+ case R: case AL:
+ if (state == SEEKING_STRONG_FOR_PARA) {
+ mParaLevel = 1;
+ state = NOT_SEEKING_STRONG;
+ } else if (state == SEEKING_STRONG_FOR_FSI) {
+ if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
+ dirProps[isolateStartStack[stackLast]] = RLI;
+ flags |= DIRPROP_FLAG(RLI);
+ }
+ state = LOOKING_FOR_PDI;
+ }
+ break;
+
+ case FSI: case LRI: case RLI:
+ stackLast++;
+ if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
+ isolateStartStack[stackLast] = i - 1;
+ previousStateStack[stackLast] = state;
+ }
+ if (dirProp == FSI) {
+ state = SEEKING_STRONG_FOR_FSI;
+ } else {
+ state = LOOKING_FOR_PDI;
+ }
+ break;
+
+ case PDI:
+ if (state == SEEKING_STRONG_FOR_FSI) {
+ if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
+ dirProps[isolateStartStack[stackLast]] = LRI;
+ flags |= DIRPROP_FLAG(LRI);
+ }
+ }
+ if (stackLast >= 0) {
+ if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
+ state = previousStateStack[stackLast];
+ }
+ stackLast--;
+ }
+ break;
+
+ case B:
+ // This shouldn't happen, since we don't support multiple paragraphs.
+ NS_NOTREACHED("Unexpected paragraph separator");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Ignore still open isolate sequences with overflow */
+ if (stackLast > NSBIDI_MAX_EXPLICIT_LEVEL) {
+ stackLast = NSBIDI_MAX_EXPLICIT_LEVEL;
+ if (dirProps[previousStateStack[NSBIDI_MAX_EXPLICIT_LEVEL]] != FSI) {
+ state = LOOKING_FOR_PDI;
+ }
+ }
+
+ /* Resolve direction of still unresolved open FSI sequences */
+ while (stackLast >= 0) {
+ if (state == SEEKING_STRONG_FOR_FSI) {
+ dirProps[isolateStartStack[stackLast]] = LRI;
+ flags |= DIRPROP_FLAG(LRI);
+ }
+ state = previousStateStack[stackLast];
+ stackLast--;
+ }
+
+ flags|=DIRPROP_FLAG_LR(mParaLevel);
+
+ mFlags = flags;
+}
+
+/* Functions for handling paired brackets ----------------------------------- */
+
+/* In the mIsoRuns array, the first entry is used for text outside of any
+ isolate sequence. Higher entries are used for each more deeply nested
+ isolate sequence.
+ mIsoRunLast is the index of the last used entry.
+ The mOpenings array is used to note the data of opening brackets not yet
+ matched by a closing bracket, or matched but still susceptible to change
+ level.
+ Each isoRun entry contains the index of the first and
+ one-after-last openings entries for pending opening brackets it
+ contains. The next mOpenings entry to use is the one-after-last of the
+ most deeply nested isoRun entry.
+ mIsoRuns entries also contain their current embedding level and the bidi
+ class of the last-encountered strong character, since these will be needed
+ to resolve the level of paired brackets. */
+
+nsBidi::BracketData::BracketData(const nsBidi *aBidi)
+{
+ mIsoRunLast = 0;
+ mIsoRuns[0].start = 0;
+ mIsoRuns[0].limit = 0;
+ mIsoRuns[0].level = aBidi->mParaLevel;
+ mIsoRuns[0].lastStrong = mIsoRuns[0].lastBase = mIsoRuns[0].contextDir =
+ GET_LR_FROM_LEVEL(aBidi->mParaLevel);
+ mIsoRuns[0].contextPos = 0;
+ mOpenings = mSimpleOpenings;
+ mOpeningsCount = SIMPLE_OPENINGS_COUNT;
+ mOpeningsMemory = nullptr;
+}
+
+nsBidi::BracketData::~BracketData()
+{
+ free(mOpeningsMemory);
+}
+
+/* LRE, LRO, RLE, RLO, PDF */
+void
+nsBidi::BracketData::ProcessBoundary(int32_t aLastDirControlCharPos,
+ nsBidiLevel aContextLevel,
+ nsBidiLevel aEmbeddingLevel,
+ const DirProp* aDirProps)
+{
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ if (DIRPROP_FLAG(aDirProps[aLastDirControlCharPos]) & MASK_ISO) { /* after an isolate */
+ return;
+ }
+ if (NO_OVERRIDE(aEmbeddingLevel) > NO_OVERRIDE(aContextLevel)) { /* not PDF */
+ aContextLevel = aEmbeddingLevel;
+ }
+ lastIsoRun.limit = lastIsoRun.start;
+ lastIsoRun.level = aEmbeddingLevel;
+ lastIsoRun.lastStrong = lastIsoRun.lastBase = lastIsoRun.contextDir =
+ GET_LR_FROM_LEVEL(aContextLevel);
+ lastIsoRun.contextPos = aLastDirControlCharPos;
+}
+
+/* LRI or RLI */
+void
+nsBidi::BracketData::ProcessLRI_RLI(nsBidiLevel aLevel)
+{
+ MOZ_ASSERT(mIsoRunLast <= NSBIDI_MAX_EXPLICIT_LEVEL);
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ lastIsoRun.lastBase = O_N;
+ IsoRun& currIsoRun = mIsoRuns[++mIsoRunLast];
+ currIsoRun.start = currIsoRun.limit = lastIsoRun.limit;
+ currIsoRun.level = aLevel;
+ currIsoRun.lastStrong = currIsoRun.lastBase = currIsoRun.contextDir =
+ GET_LR_FROM_LEVEL(aLevel);
+ currIsoRun.contextPos = 0;
+}
+
+/* PDI */
+void
+nsBidi::BracketData::ProcessPDI()
+{
+ MOZ_ASSERT(mIsoRunLast > 0);
+ mIsoRuns[--mIsoRunLast].lastBase = O_N;
+}
+
+/* newly found opening bracket: create an openings entry */
+bool /* return true if success */
+nsBidi::BracketData::AddOpening(char16_t aMatch, int32_t aPosition)
+{
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ if (lastIsoRun.limit >= mOpeningsCount) { /* no available new entry */
+ if (!GETOPENINGSMEMORY(lastIsoRun.limit * 2)) {
+ return false;
+ }
+ if (mOpenings == mSimpleOpenings) {
+ memcpy(mOpeningsMemory, mSimpleOpenings,
+ SIMPLE_OPENINGS_COUNT * sizeof(Opening));
+ }
+ mOpenings = mOpeningsMemory; /* may have changed */
+ mOpeningsCount = mOpeningsSize / sizeof(Opening);
+ }
+ Opening& o = mOpenings[lastIsoRun.limit];
+ o.position = aPosition;
+ o.match = aMatch;
+ o.contextDir = lastIsoRun.contextDir;
+ o.contextPos = lastIsoRun.contextPos;
+ o.flags = 0;
+ lastIsoRun.limit++;
+ return true;
+}
+
+/* change N0c1 to N0c2 when a preceding bracket is assigned the embedding level */
+void
+nsBidi::BracketData::FixN0c(int32_t aOpeningIndex, int32_t aNewPropPosition,
+ DirProp aNewProp, DirProp* aDirProps)
+{
+ /* This function calls itself recursively */
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ for (int32_t k = aOpeningIndex + 1; k < lastIsoRun.limit; k++) {
+ Opening& o = mOpenings[k];
+ if (o.match >= 0) { /* not an N0c match */
+ continue;
+ }
+ if (aNewPropPosition < o.contextPos) {
+ break;
+ }
+ int32_t openingPosition = o.position;
+ if (aNewPropPosition >= openingPosition) {
+ continue;
+ }
+ if (aNewProp == o.contextDir) {
+ break;
+ }
+ aDirProps[openingPosition] = aNewProp;
+ int32_t closingPosition = -(o.match);
+ aDirProps[closingPosition] = aNewProp;
+ o.match = 0; /* prevent further changes */
+ FixN0c(k, openingPosition, aNewProp, aDirProps);
+ FixN0c(k, closingPosition, aNewProp, aDirProps);
+ }
+}
+
+/* process closing bracket */
+DirProp /* return L or R if N0b or N0c, ON if N0d */
+nsBidi::BracketData::ProcessClosing(int32_t aOpenIdx, int32_t aPosition,
+ DirProp* aDirProps)
+{
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ Opening& o = mOpenings[aOpenIdx];
+ DirProp newProp;
+ DirProp direction = GET_LR_FROM_LEVEL(lastIsoRun.level);
+ bool stable = true; // assume stable until proved otherwise
+
+ /* The stable flag is set when brackets are paired and their
+ level is resolved and cannot be changed by what will be
+ found later in the source string.
+ An unstable match can occur only when applying N0c, where
+ the resolved level depends on the preceding context, and
+ this context may be affected by text occurring later.
+ Example: RTL paragraph containing: abc[(latin) HEBREW]
+ When the closing parenthesis is encountered, it appears
+ that N0c1 must be applied since 'abc' sets an opposite
+ direction context and both parentheses receive level 2.
+ However, when the closing square bracket is processed,
+ N0b applies because of 'HEBREW' being included within the
+ brackets, thus the square brackets are treated like R and
+ receive level 1. However, this changes the preceding
+ context of the opening parenthesis, and it now appears
+ that N0c2 must be applied to the parentheses rather than
+ N0c1. */
+
+ if ((direction == 0 && o.flags & FOUND_L) ||
+ (direction == 1 && o.flags & FOUND_R)) { /* N0b */
+ newProp = direction;
+ } else if (o.flags & (FOUND_L|FOUND_R)) { /* N0c */
+ /* it is stable if there is no containing pair or in
+ conditions too complicated and not worth checking */
+ stable = (aOpenIdx == lastIsoRun.start);
+ if (direction != o.contextDir) {
+ newProp = o.contextDir; /* N0c1 */
+ } else {
+ newProp = direction; /* N0c2 */
+ }
+ } else {
+ /* forget this and any brackets nested within this pair */
+ lastIsoRun.limit = aOpenIdx;
+ return O_N; /* N0d */
+ }
+ aDirProps[o.position] = newProp;
+ aDirProps[aPosition] = newProp;
+ /* Update nested N0c pairs that may be affected */
+ FixN0c(aOpenIdx, o.position, newProp, aDirProps);
+ if (stable) {
+ /* forget any brackets nested within this pair */
+ lastIsoRun.limit = aOpenIdx;
+ } else {
+ int32_t k;
+ o.match = -aPosition;
+ /* neutralize any unmatched opening between the current pair */
+ for (k = aOpenIdx + 1; k < lastIsoRun.limit; k++) {
+ Opening& oo = mOpenings[k];
+ if (oo.position > aPosition) {
+ break;
+ }
+ if (oo.match > 0) {
+ oo.match = 0;
+ }
+ }
+ }
+ return newProp;
+}
+
+static inline bool
+IsMatchingCloseBracket(char16_t aCh1, char16_t aCh2)
+{
+ // U+232A RIGHT-POINTING ANGLE BRACKET and U+3009 RIGHT ANGLE BRACKET
+ // are canonical equivalents, so we special-case them here.
+ return (aCh1 == aCh2) ||
+ (aCh1 == 0x232A && aCh2 == 0x3009) ||
+ (aCh2 == 0x232A && aCh1 == 0x3009);
+}
+
+/* Handle strong characters, digits and candidates for closing brackets. */
+/* Returns true if success. (The only failure mode is an OOM when trying
+ to allocate memory for the Openings array.) */
+bool
+nsBidi::BracketData::ProcessChar(int32_t aPosition, char16_t aCh,
+ DirProp* aDirProps, nsBidiLevel* aLevels)
+{
+ IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+ DirProp newProp;
+ DirProp dirProp = aDirProps[aPosition];
+ nsBidiLevel level = aLevels[aPosition];
+ if (dirProp == O_N) {
+ /* First see if it is a matching closing bracket. Hopefully, this is
+ more efficient than checking if it is a closing bracket at all */
+ for (int32_t idx = lastIsoRun.limit - 1; idx >= lastIsoRun.start; idx--) {
+ if (!IsMatchingCloseBracket(aCh, mOpenings[idx].match)) {
+ continue;
+ }
+ /* We have a match */
+ newProp = ProcessClosing(idx, aPosition, aDirProps);
+ if (newProp == O_N) { /* N0d */
+ aCh = 0; /* prevent handling as an opening */
+ break;
+ }
+ lastIsoRun.lastBase = O_N;
+ lastIsoRun.contextDir = newProp;
+ lastIsoRun.contextPos = aPosition;
+ if (level & NSBIDI_LEVEL_OVERRIDE) { /* X4, X5 */
+ newProp = GET_LR_FROM_LEVEL(level);
+ lastIsoRun.lastStrong = newProp;
+ uint16_t flag = DIRPROP_FLAG(newProp);
+ for (int32_t i = lastIsoRun.start; i < idx; i++) {
+ mOpenings[i].flags |= flag;
+ }
+ /* matching brackets are not overridden by LRO/RLO */
+ aLevels[aPosition] &= ~NSBIDI_LEVEL_OVERRIDE;
+ }
+ /* matching brackets are not overridden by LRO/RLO */
+ aLevels[mOpenings[idx].position] &= ~NSBIDI_LEVEL_OVERRIDE;
+ return true;
+ }
+ /* We get here only if the ON character is not a matching closing
+ bracket or it is a case of N0d */
+ /* Now see if it is an opening bracket */
+ char16_t match = GetPairedBracket(aCh);
+ if (match != aCh && /* has a matching char */
+ GetPairedBracketType(aCh) == PAIRED_BRACKET_TYPE_OPEN) { /* opening bracket */
+ if (!AddOpening(match, aPosition)) {
+ return false;
+ }
+ }
+ }
+ if (level & NSBIDI_LEVEL_OVERRIDE) { /* X4, X5 */
+ newProp = GET_LR_FROM_LEVEL(level);
+ if (dirProp != S && dirProp != WS && dirProp != O_N) {
+ aDirProps[aPosition] = newProp;
+ }
+ lastIsoRun.lastBase = newProp;
+ lastIsoRun.lastStrong = newProp;
+ lastIsoRun.contextDir = newProp;
+ lastIsoRun.contextPos = aPosition;
+ } else if (IS_STRONG_TYPE(dirProp)) {
+ newProp = DirFromStrong(dirProp);
+ lastIsoRun.lastBase = dirProp;
+ lastIsoRun.lastStrong = dirProp;
+ lastIsoRun.contextDir = newProp;
+ lastIsoRun.contextPos = aPosition;
+ } else if (dirProp == EN) {
+ lastIsoRun.lastBase = EN;
+ if (lastIsoRun.lastStrong == L) {
+ newProp = L; /* W7 */
+ aDirProps[aPosition] = ENL;
+ lastIsoRun.contextDir = L;
+ lastIsoRun.contextPos = aPosition;
+ } else {
+ newProp = R; /* N0 */
+ if (lastIsoRun.lastStrong == AL) {
+ aDirProps[aPosition] = AN; /* W2 */
+ } else {
+ aDirProps[aPosition] = ENR;
+ }
+ lastIsoRun.contextDir = R;
+ lastIsoRun.contextPos = aPosition;
+ }
+ } else if (dirProp == AN) {
+ newProp = R; /* N0 */
+ lastIsoRun.lastBase = AN;
+ lastIsoRun.contextDir = R;
+ lastIsoRun.contextPos = aPosition;
+ } else if (dirProp == NSM) {
+ /* if the last real char was ON, change NSM to ON so that it
+ will stay ON even if the last real char is a bracket which
+ may be changed to L or R */
+ newProp = lastIsoRun.lastBase;
+ if (newProp == O_N) {
+ aDirProps[aPosition] = newProp;
+ }
+ } else {
+ newProp = dirProp;
+ lastIsoRun.lastBase = dirProp;
+ }
+ if (IS_STRONG_TYPE(newProp)) {
+ uint16_t flag = DIRPROP_FLAG(DirFromStrong(newProp));
+ for (int32_t i = lastIsoRun.start; i < lastIsoRun.limit; i++) {
+ if (aPosition > mOpenings[i].position) {
+ mOpenings[i].flags |= flag;
+ }
+ }
+ }
+ return true;
+}
+
+/* perform (X1)..(X9) ------------------------------------------------------- */
+
+/*
+ * Resolve the explicit levels as specified by explicit embedding codes.
+ * Recalculate the flags to have them reflect the real properties
+ * after taking the explicit embeddings into account.
+ *
+ * The Bidi algorithm is designed to result in the same behavior whether embedding
+ * levels are externally specified (from "styled text", supposedly the preferred
+ * method) or set by explicit embedding codes (LRx, RLx, PDF, FSI, PDI) in the plain text.
+ * That is why (X9) instructs to remove all not-isolate explicit codes (and BN).
+ * However, in a real implementation, this removal of these codes and their index
+ * positions in the plain text is undesirable since it would result in
+ * reallocated, reindexed text.
+ * Instead, this implementation leaves the codes in there and just ignores them
+ * in the subsequent processing.
+ * In order to get the same reordering behavior, positions with a BN or a not-isolate
+ * explicit embedding code just get the same level assigned as the last "real"
+ * character.
+ *
+ * Some implementations, not this one, then overwrite some of these
+ * directionality properties at "real" same-level-run boundaries by
+ * L or R codes so that the resolution of weak types can be performed on the
+ * entire paragraph at once instead of having to parse it once more and
+ * perform that resolution on same-level-runs.
+ * This limits the scope of the implicit rules in effectively
+ * the same way as the run limits.
+ *
+ * Instead, this implementation does not modify these codes.
+ * On one hand, the paragraph has to be scanned for same-level-runs, but
+ * on the other hand, this saves another loop to reset these codes,
+ * or saves making and modifying a copy of dirProps[].
+ *
+ *
+ * Note that (Pn) and (Xn) changed significantly from version 4 of the Bidi algorithm.
+ *
+ *
+ * Handling the stack of explicit levels (Xn):
+ *
+ * With the Bidi stack of explicit levels, as pushed with each
+ * LRE, RLE, LRO, and RLO, LRI, RLI, and FSI and popped with each PDF and PDI,
+ * the explicit level must never exceed NSBIDI_MAX_EXPLICIT_LEVEL.
+ *
+ * In order to have a correct push-pop semantics even in the case of overflows,
+ * overflow counters and a valid isolate counter are used as described in UAX#9
+ * section 3.3.2 "Explicit Levels and Direction".
+ *
+ * This implementation assumes that NSBIDI_MAX_EXPLICIT_LEVEL is odd.
+ */
+
+void nsBidi::ResolveExplicitLevels(nsBidiDirection *aDirection, const char16_t *aText)
+{
+ DirProp *dirProps=mDirProps;
+ nsBidiLevel *levels=mLevels;
+
+ int32_t i=0, length=mLength;
+ Flags flags=mFlags; /* collect all directionalities in the text */
+ DirProp dirProp;
+ nsBidiLevel level=mParaLevel;
+ nsBidiDirection direction;
+
+ mIsolateCount = 0;
+
+ /* determine if the text is mixed-directional or single-directional */
+ direction=DirectionFromFlags(flags);
+
+ /* we may not need to resolve any explicit levels */
+ if(direction!=NSBIDI_MIXED) {
+ /* not mixed directionality: levels don't matter - trailingWSStart will be 0 */
+ } else if(!(flags&(MASK_EXPLICIT|MASK_ISO))) {
+ BracketData bracketData(this);
+ /* no embeddings, set all levels to the paragraph level */
+ for(i=0; i<length; ++i) {
+ levels[i]=level;
+ if (dirProps[i] == BN) {
+ continue;
+ }
+ if (!bracketData.ProcessChar(i, aText[i], mDirProps, mLevels)) {
+ NS_WARNING("BracketData::ProcessChar failed, out of memory?");
+ // Ran out of memory for deeply-nested openings; give up and
+ // return LTR. This could presumably result in incorrect display,
+ // but in practice it won't happen except in some artificially-
+ // constructed torture test -- which is just as likely to die
+ // altogether with an OOM failure.
+ *aDirection = NSBIDI_LTR;
+ return;
+ }
+ }
+ } else {
+ /* continue to perform (Xn) */
+
+ /* (X1) level is set for all codes, embeddingLevel keeps track of the push/pop operations */
+ /* both variables may carry the NSBIDI_LEVEL_OVERRIDE flag to indicate the override status */
+ nsBidiLevel embeddingLevel = level, newLevel;
+ nsBidiLevel previousLevel = level; /* previous level for regular (not CC) characters */
+ int32_t lastDirControlCharPos = 0; /* index of last effective LRx,RLx, PDx */
+
+ uint16_t stack[NSBIDI_MAX_EXPLICIT_LEVEL + 2]; /* we never push anything >=NSBIDI_MAX_EXPLICIT_LEVEL
+ but we need one more entry as base */
+ int32_t stackLast = 0;
+ int32_t overflowIsolateCount = 0;
+ int32_t overflowEmbeddingCount = 0;
+ int32_t validIsolateCount = 0;
+
+ BracketData bracketData(this);
+
+ stack[0] = level;
+
+ /* recalculate the flags */
+ flags=0;
+
+ /* since we assume that this is a single paragraph, we ignore (X8) */
+ for(i=0; i<length; ++i) {
+ dirProp=dirProps[i];
+ switch(dirProp) {
+ case LRE:
+ case RLE:
+ case LRO:
+ case RLO:
+ /* (X2, X3, X4, X5) */
+ flags |= DIRPROP_FLAG(BN);
+ levels[i] = previousLevel;
+ if (dirProp == LRE || dirProp == LRO) {
+ newLevel = (embeddingLevel + 2) & ~(NSBIDI_LEVEL_OVERRIDE | 1); /* least greater even level */
+ } else {
+ newLevel = ((embeddingLevel & ~NSBIDI_LEVEL_OVERRIDE) + 1) | 1; /* least greater odd level */
+ }
+ if(newLevel <= NSBIDI_MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && overflowEmbeddingCount == 0) {
+ lastDirControlCharPos = i;
+ embeddingLevel = newLevel;
+ if (dirProp == LRO || dirProp == RLO) {
+ embeddingLevel |= NSBIDI_LEVEL_OVERRIDE;
+ }
+ stackLast++;
+ stack[stackLast] = embeddingLevel;
+ /* we don't need to set NSBIDI_LEVEL_OVERRIDE off for LRE and RLE
+ since this has already been done for newLevel which is
+ the source for embeddingLevel.
+ */
+ } else {
+ if (overflowIsolateCount == 0) {
+ overflowEmbeddingCount++;
+ }
+ }
+ break;
+
+ case PDF:
+ /* (X7) */
+ flags |= DIRPROP_FLAG(BN);
+ levels[i] = previousLevel;
+ /* handle all the overflow cases first */
+ if (overflowIsolateCount) {
+ break;
+ }
+ if (overflowEmbeddingCount) {
+ overflowEmbeddingCount--;
+ break;
+ }
+ if (stackLast > 0 && stack[stackLast] < ISOLATE) { /* not an isolate entry */
+ lastDirControlCharPos = i;
+ stackLast--;
+ embeddingLevel = stack[stackLast];
+ }
+ break;
+
+ case LRI:
+ case RLI:
+ flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG_LR(embeddingLevel);
+ levels[i] = NO_OVERRIDE(embeddingLevel);
+ if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+ bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+ embeddingLevel, mDirProps);
+ flags |= DIRPROP_FLAG_MULTI_RUNS;
+ }
+ previousLevel = embeddingLevel;
+ /* (X5a, X5b) */
+ if (dirProp == LRI) {
+ newLevel = (embeddingLevel + 2) & ~(NSBIDI_LEVEL_OVERRIDE | 1); /* least greater even level */
+ } else {
+ newLevel = ((embeddingLevel & ~NSBIDI_LEVEL_OVERRIDE) + 1) | 1; /* least greater odd level */
+ }
+ if (newLevel <= NSBIDI_MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && overflowEmbeddingCount == 0) {
+ flags |= DIRPROP_FLAG(dirProp);
+ lastDirControlCharPos = i;
+ previousLevel = embeddingLevel;
+ validIsolateCount++;
+ if (validIsolateCount > mIsolateCount) {
+ mIsolateCount = validIsolateCount;
+ }
+ embeddingLevel = newLevel;
+ stackLast++;
+ stack[stackLast] = embeddingLevel + ISOLATE;
+ bracketData.ProcessLRI_RLI(embeddingLevel);
+ } else {
+ /* make it so that it is handled by AdjustWSLevels() */
+ dirProps[i] = WS;
+ overflowIsolateCount++;
+ }
+ break;
+
+ case PDI:
+ if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+ bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+ embeddingLevel, mDirProps);
+ flags |= DIRPROP_FLAG_MULTI_RUNS;
+ }
+ /* (X6a) */
+ if (overflowIsolateCount) {
+ overflowIsolateCount--;
+ /* make it so that it is handled by AdjustWSLevels() */
+ dirProps[i] = WS;
+ } else if (validIsolateCount) {
+ flags |= DIRPROP_FLAG(PDI);
+ lastDirControlCharPos = i;
+ overflowEmbeddingCount = 0;
+ while (stack[stackLast] < ISOLATE) {
+ /* pop embedding entries */
+ /* until the last isolate entry */
+ stackLast--;
+
+ // Since validIsolateCount is true, there must be an isolate entry
+ // on the stack, so the stack is guaranteed to not be empty.
+ // Still, to eliminate a warning from coverity, we use an assertion.
+ MOZ_ASSERT(stackLast > 0);
+ }
+ stackLast--; /* pop also the last isolate entry */
+ MOZ_ASSERT(stackLast >= 0); // For coverity
+ validIsolateCount--;
+ bracketData.ProcessPDI();
+ } else {
+ /* make it so that it is handled by AdjustWSLevels() */
+ dirProps[i] = WS;
+ }
+ embeddingLevel = stack[stackLast] & ~ISOLATE;
+ flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG_LR(embeddingLevel);
+ previousLevel = embeddingLevel;
+ levels[i] = NO_OVERRIDE(embeddingLevel);
+ break;
+
+ case B:
+ /*
+ * We do not expect to see a paragraph separator (B),
+ */
+ NS_NOTREACHED("Unexpected paragraph separator");
+ break;
+
+ case BN:
+ /* BN, LRE, RLE, and PDF are supposed to be removed (X9) */
+ /* they will get their levels set correctly in AdjustWSLevels() */
+ levels[i] = previousLevel;
+ flags |= DIRPROP_FLAG(BN);
+ break;
+
+ default:
+ /* all other types get the "real" level */
+ if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+ bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+ embeddingLevel, mDirProps);
+ flags |= DIRPROP_FLAG_MULTI_RUNS;
+ if (embeddingLevel & NSBIDI_LEVEL_OVERRIDE) {
+ flags |= DIRPROP_FLAG_O(embeddingLevel);
+ } else {
+ flags |= DIRPROP_FLAG_E(embeddingLevel);
+ }
+ }
+ previousLevel = embeddingLevel;
+ levels[i] = embeddingLevel;
+ if (!bracketData.ProcessChar(i, aText[i], mDirProps, mLevels)) {
+ NS_WARNING("BracketData::ProcessChar failed, out of memory?");
+ *aDirection = NSBIDI_LTR;
+ return;
+ }
+ flags |= DIRPROP_FLAG(dirProps[i]);
+ break;
+ }
+ }
+
+ if(flags&MASK_EMBEDDING) {
+ flags|=DIRPROP_FLAG_LR(mParaLevel);
+ }
+
+ /* subsequently, ignore the explicit codes and BN (X9) */
+
+ /* again, determine if the text is mixed-directional or single-directional */
+ mFlags=flags;
+ direction=DirectionFromFlags(flags);
+ }
+
+ *aDirection = direction;
+}
+
+/* determine if the text is mixed-directional or single-directional */
+nsBidiDirection nsBidi::DirectionFromFlags(Flags aFlags)
+{
+ /* if the text contains AN and neutrals, then some neutrals may become RTL */
+ if(!(aFlags&MASK_RTL || (aFlags&DIRPROP_FLAG(AN) && aFlags&MASK_POSSIBLE_N))) {
+ return NSBIDI_LTR;
+ } else if(!(aFlags&MASK_LTR)) {
+ return NSBIDI_RTL;
+ } else {
+ return NSBIDI_MIXED;
+ }
+}
+
+/******************************************************************
+ The Properties state machine table
+*******************************************************************
+
+ All table cells are 8 bits:
+ bits 0..4: next state
+ bits 5..7: action to perform (if > 0)
+
+ Cells may be of format "n" where n represents the next state
+ (except for the rightmost column).
+ Cells may also be of format "s(x,y)" where x represents an action
+ to perform and y represents the next state.
+
+*******************************************************************
+ Definitions and type for properties state table
+*******************************************************************
+*/
+#define IMPTABPROPS_COLUMNS 16
+#define IMPTABPROPS_RES (IMPTABPROPS_COLUMNS - 1)
+#define GET_STATEPROPS(cell) ((cell)&0x1f)
+#define GET_ACTIONPROPS(cell) ((cell)>>5)
+#undef s
+#define s(action, newState) ((uint8_t)(newState+(action<<5)))
+
+static const uint8_t groupProp[] = /* dirProp regrouped */
+{
+/* L R EN ES ET AN CS B S WS ON LRE LRO AL RLE RLO PDF NSM BN FSI LRI RLI PDI ENL ENR */
+ 0, 1, 2, 7, 8, 3, 9, 6, 5, 4, 4, 10, 10, 12, 10, 10, 10, 11, 10, 4, 4, 4, 4, 13, 14
+};
+
+/******************************************************************
+
+ PROPERTIES STATE TABLE
+
+ In table impTabProps,
+ - the ON column regroups ON and WS, FSI, RLI, LRI and PDI
+ - the BN column regroups BN, LRE, RLE, LRO, RLO, PDF
+ - the Res column is the reduced property assigned to a run
+
+ Action 1: process current run1, init new run1
+ 2: init new run2
+ 3: process run1, process run2, init new run1
+ 4: process run1, set run1=run2, init new run2
+
+ Notes:
+ 1) This table is used in ResolveImplicitLevels().
+ 2) This table triggers actions when there is a change in the Bidi
+ property of incoming characters (action 1).
+ 3) Most such property sequences are processed immediately (in
+ fact, passed to ProcessPropertySeq().
+ 4) However, numbers are assembled as one sequence. This means
+ that undefined situations (like CS following digits, until
+ it is known if the next char will be a digit) are held until
+ following chars define them.
+ Example: digits followed by CS, then comes another CS or ON;
+ the digits will be processed, then the CS assigned
+ as the start of an ON sequence (action 3).
+ 5) There are cases where more than one sequence must be
+ processed, for instance digits followed by CS followed by L:
+ the digits must be processed as one sequence, and the CS
+ must be processed as an ON sequence, all this before starting
+ assembling chars for the opening L sequence.
+
+
+*/
+static const uint8_t impTabProps[][IMPTABPROPS_COLUMNS] =
+{
+/* L , R , EN , AN , ON , S , B , ES , ET , CS , BN , NSM , AL , ENL , ENR , Res */
+/* 0 Init */ { 1 , 2 , 4 , 5 , 7 , 15 , 17 , 7 , 9 , 7 , 0 , 7 , 3 , 18 , 21 , DirProp_ON },
+/* 1 L */ { 1 , s(1,2), s(1,4), s(1,5), s(1,7),s(1,15),s(1,17), s(1,7), s(1,9), s(1,7), 1 , 1 , s(1,3),s(1,18),s(1,21), DirProp_L },
+/* 2 R */ { s(1,1), 2 , s(1,4), s(1,5), s(1,7),s(1,15),s(1,17), s(1,7), s(1,9), s(1,7), 2 , 2 , s(1,3),s(1,18),s(1,21), DirProp_R },
+/* 3 AL */ { s(1,1), s(1,2), s(1,6), s(1,6), s(1,8),s(1,16),s(1,17), s(1,8), s(1,8), s(1,8), 3 , 3 , 3 ,s(1,18),s(1,21), DirProp_R },
+/* 4 EN */ { s(1,1), s(1,2), 4 , s(1,5), s(1,7),s(1,15),s(1,17),s(2,10), 11 ,s(2,10), 4 , 4 , s(1,3), 18 , 21 , DirProp_EN },
+/* 5 AN */ { s(1,1), s(1,2), s(1,4), 5 , s(1,7),s(1,15),s(1,17), s(1,7), s(1,9),s(2,12), 5 , 5 , s(1,3),s(1,18),s(1,21), DirProp_AN },
+/* 6 AL:EN/AN */ { s(1,1), s(1,2), 6 , 6 , s(1,8),s(1,16),s(1,17), s(1,8), s(1,8),s(2,13), 6 , 6 , s(1,3), 18 , 21 , DirProp_AN },
+/* 7 ON */ { s(1,1), s(1,2), s(1,4), s(1,5), 7 ,s(1,15),s(1,17), 7 ,s(2,14), 7 , 7 , 7 , s(1,3),s(1,18),s(1,21), DirProp_ON },
+/* 8 AL:ON */ { s(1,1), s(1,2), s(1,6), s(1,6), 8 ,s(1,16),s(1,17), 8 , 8 , 8 , 8 , 8 , s(1,3),s(1,18),s(1,21), DirProp_ON },
+/* 9 ET */ { s(1,1), s(1,2), 4 , s(1,5), 7 ,s(1,15),s(1,17), 7 , 9 , 7 , 9 , 9 , s(1,3), 18 , 21 , DirProp_ON },
+/*10 EN+ES/CS */ { s(3,1), s(3,2), 4 , s(3,5), s(4,7),s(3,15),s(3,17), s(4,7),s(4,14), s(4,7), 10 , s(4,7), s(3,3), 18 , 21 , DirProp_EN },
+/*11 EN+ET */ { s(1,1), s(1,2), 4 , s(1,5), s(1,7),s(1,15),s(1,17), s(1,7), 11 , s(1,7), 11 , 11 , s(1,3), 18 , 21 , DirProp_EN },
+/*12 AN+CS */ { s(3,1), s(3,2), s(3,4), 5 , s(4,7),s(3,15),s(3,17), s(4,7),s(4,14), s(4,7), 12 , s(4,7), s(3,3),s(3,18),s(3,21), DirProp_AN },
+/*13 AL:EN/AN+CS */ { s(3,1), s(3,2), 6 , 6 , s(4,8),s(3,16),s(3,17), s(4,8), s(4,8), s(4,8), 13 , s(4,8), s(3,3), 18 , 21 , DirProp_AN },
+/*14 ON+ET */ { s(1,1), s(1,2), s(4,4), s(1,5), 7 ,s(1,15),s(1,17), 7 , 14 , 7 , 14 , 14 , s(1,3),s(4,18),s(4,21), DirProp_ON },
+/*15 S */ { s(1,1), s(1,2), s(1,4), s(1,5), s(1,7), 15 ,s(1,17), s(1,7), s(1,9), s(1,7), 15 , s(1,7), s(1,3),s(1,18),s(1,21), DirProp_S },
+/*16 AL:S */ { s(1,1), s(1,2), s(1,6), s(1,6), s(1,8), 16 ,s(1,17), s(1,8), s(1,8), s(1,8), 16 , s(1,8), s(1,3),s(1,18),s(1,21), DirProp_S },
+/*17 B */ { s(1,1), s(1,2), s(1,4), s(1,5), s(1,7),s(1,15), 17 , s(1,7), s(1,9), s(1,7), 17 , s(1,7), s(1,3),s(1,18),s(1,21), DirProp_B },
+/*18 ENL */ { s(1,1), s(1,2), 18 , s(1,5), s(1,7),s(1,15),s(1,17),s(2,19), 20 ,s(2,19), 18 , 18 , s(1,3), 18 , 21 , DirProp_L },
+/*19 ENL+ES/CS */ { s(3,1), s(3,2), 18 , s(3,5), s(4,7),s(3,15),s(3,17), s(4,7),s(4,14), s(4,7), 19 , s(4,7), s(3,3), 18 , 21 , DirProp_L },
+/*20 ENL+ET */ { s(1,1), s(1,2), 18 , s(1,5), s(1,7),s(1,15),s(1,17), s(1,7), 20 , s(1,7), 20 , 20 , s(1,3), 18 , 21 , DirProp_L },
+/*21 ENR */ { s(1,1), s(1,2), 21 , s(1,5), s(1,7),s(1,15),s(1,17),s(2,22), 23 ,s(2,22), 21 , 21 , s(1,3), 18 , 21 , DirProp_AN },
+/*22 ENR+ES/CS */ { s(3,1), s(3,2), 21 , s(3,5), s(4,7),s(3,15),s(3,17), s(4,7),s(4,14), s(4,7), 22 , s(4,7), s(3,3), 18 , 21 , DirProp_AN },
+/*23 ENR+ET */ { s(1,1), s(1,2), 21 , s(1,5), s(1,7),s(1,15),s(1,17), s(1,7), 23 , s(1,7), 23 , 23 , s(1,3), 18 , 21 , DirProp_AN }
+};
+
+/* we must undef macro s because the levels table have a different
+ * structure (4 bits for action and 4 bits for next state.
+ */
+#undef s
+
+/******************************************************************
+ The levels state machine tables
+*******************************************************************
+
+ All table cells are 8 bits:
+ bits 0..3: next state
+ bits 4..7: action to perform (if > 0)
+
+ Cells may be of format "n" where n represents the next state
+ (except for the rightmost column).
+ Cells may also be of format "s(x,y)" where x represents an action
+ to perform and y represents the next state.
+
+ This format limits each table to 16 states each and to 15 actions.
+
+*******************************************************************
+ Definitions and type for levels state tables
+*******************************************************************
+*/
+#define IMPTABLEVELS_RES (IMPTABLEVELS_COLUMNS - 1)
+#define GET_STATE(cell) ((cell)&0x0f)
+#define GET_ACTION(cell) ((cell)>>4)
+#define s(action, newState) ((uint8_t)(newState+(action<<4)))
+
+/******************************************************************
+
+ LEVELS STATE TABLES
+
+ In all levels state tables,
+ - state 0 is the initial state
+ - the Res column is the increment to add to the text level
+ for this property sequence.
+
+ The impAct arrays for each table of a pair map the local action
+ numbers of the table to the total list of actions. For instance,
+ action 2 in a given table corresponds to the action number which
+ appears in entry [2] of the impAct array for that table.
+ The first entry of all impAct arrays must be 0.
+
+ Action 1: init conditional sequence
+ 2: prepend conditional sequence to current sequence
+ 3: set ON sequence to new level - 1
+ 4: init EN/AN/ON sequence
+ 5: fix EN/AN/ON sequence followed by R
+ 6: set previous level sequence to level 2
+
+ Notes:
+ 1) These tables are used in ProcessPropertySeq(). The input
+ is property sequences as determined by ResolveImplicitLevels.
+ 2) Most such property sequences are processed immediately
+ (levels are assigned).
+ 3) However, some sequences cannot be assigned a final level till
+ one or more following sequences are received. For instance,
+ ON following an R sequence within an even-level paragraph.
+ If the following sequence is R, the ON sequence will be
+ assigned basic run level+1, and so will the R sequence.
+ 4) S is generally handled like ON, since its level will be fixed
+ to paragraph level in AdjustWSLevels().
+
+*/
+
+static const ImpTab impTabL = /* Even paragraph level */
+/* In this table, conditional sequences receive the higher possible level
+ until proven otherwise.
+*/
+{
+/* L , R , EN , AN , ON , S , B , Res */
+/* 0 : init */ { 0 , 1 , 0 , 2 , 0 , 0 , 0 , 0 },
+/* 1 : R */ { 0 , 1 , 3 , 3 , s(1,4), s(1,4), 0 , 1 },
+/* 2 : AN */ { 0 , 1 , 0 , 2 , s(1,5), s(1,5), 0 , 2 },
+/* 3 : R+EN/AN */ { 0 , 1 , 3 , 3 , s(1,4), s(1,4), 0 , 2 },
+/* 4 : R+ON */ { s(2,0), 1 , 3 , 3 , 4 , 4 , s(2,0), 1 },
+/* 5 : AN+ON */ { s(2,0), 1 , s(2,0), 2 , 5 , 5 , s(2,0), 1 }
+};
+static const ImpTab impTabR = /* Odd paragraph level */
+/* In this table, conditional sequences receive the lower possible level
+ until proven otherwise.
+*/
+{
+/* L , R , EN , AN , ON , S , B , Res */
+/* 0 : init */ { 1 , 0 , 2 , 2 , 0 , 0 , 0 , 0 },
+/* 1 : L */ { 1 , 0 , 1 , 3 , s(1,4), s(1,4), 0 , 1 },
+/* 2 : EN/AN */ { 1 , 0 , 2 , 2 , 0 , 0 , 0 , 1 },
+/* 3 : L+AN */ { 1 , 0 , 1 , 3 , 5 , 5 , 0 , 1 },
+/* 4 : L+ON */ { s(2,1), 0 , s(2,1), 3 , 4 , 4 , 0 , 0 },
+/* 5 : L+AN+ON */ { 1 , 0 , 1 , 3 , 5 , 5 , 0 , 0 }
+};
+
+#undef s
+
+static ImpAct impAct0 = {0,1,2,3,4,5,6};
+static PImpTab impTab[2] = {impTabL, impTabR};
+
+/*------------------------------------------------------------------------*/
+
+/* perform rules (Wn), (Nn), and (In) on a run of the text ------------------ */
+
+/*
+ * This implementation of the (Wn) rules applies all rules in one pass.
+ * In order to do so, it needs a look-ahead of typically 1 character
+ * (except for W5: sequences of ET) and keeps track of changes
+ * in a rule Wp that affect a later Wq (p<q).
+ *
+ * The (Nn) and (In) rules are also performed in that same single loop,
+ * but effectively one iteration behind for white space.
+ *
+ * Since all implicit rules are performed in one step, it is not necessary
+ * to actually store the intermediate directional properties in dirProps[].
+ */
+
+void nsBidi::ProcessPropertySeq(LevState *pLevState, uint8_t _prop, int32_t start, int32_t limit)
+{
+ uint8_t cell, oldStateSeq, actionSeq;
+ PImpTab pImpTab = pLevState->pImpTab;
+ PImpAct pImpAct = pLevState->pImpAct;
+ nsBidiLevel* levels = mLevels;
+ nsBidiLevel level, addLevel;
+ int32_t start0, k;
+
+ start0 = start; /* save original start position */
+ oldStateSeq = (uint8_t)pLevState->state;
+ cell = pImpTab[oldStateSeq][_prop];
+ pLevState->state = GET_STATE(cell); /* isolate the new state */
+ actionSeq = pImpAct[GET_ACTION(cell)]; /* isolate the action */
+ addLevel = pImpTab[pLevState->state][IMPTABLEVELS_RES];
+
+ if(actionSeq) {
+ switch(actionSeq) {
+ case 1: /* init ON seq */
+ pLevState->startON = start0;
+ break;
+
+ case 2: /* prepend ON seq to current seq */
+ MOZ_ASSERT(pLevState->startON >= 0, "no valid ON sequence start!");
+ start = pLevState->startON;
+ break;
+
+ default: /* we should never get here */
+ MOZ_ASSERT(false);
+ break;
+ }
+ }
+ if(addLevel || (start < start0)) {
+ level = pLevState->runLevel + addLevel;
+ if (start >= pLevState->runStart) {
+ for (k = start; k < limit; k++) {
+ levels[k] = level;
+ }
+ } else {
+ DirProp *dirProps = mDirProps, dirProp;
+ int32_t isolateCount = 0;
+ for (k = start; k < limit; k++) {
+ dirProp = dirProps[k];
+ if (dirProp == PDI) {
+ isolateCount--;
+ }
+ if (isolateCount == 0) {
+ levels[k]=level;
+ }
+ if (dirProp == LRI || dirProp == RLI) {
+ isolateCount++;
+ }
+ }
+ }
+ }
+}
+
+void nsBidi::ResolveImplicitLevels(int32_t aStart, int32_t aLimit,
+ DirProp aSOR, DirProp aEOR)
+{
+ const DirProp *dirProps = mDirProps;
+ DirProp dirProp;
+ LevState levState;
+ int32_t i, start1, start2;
+ uint16_t oldStateImp, stateImp, actionImp;
+ uint8_t gprop, resProp, cell;
+
+ /* initialize for property and levels state tables */
+ levState.runStart = aStart;
+ levState.runLevel = mLevels[aStart];
+ levState.pImpTab = impTab[levState.runLevel & 1];
+ levState.pImpAct = impAct0;
+ levState.startON = -1; /* initialize to invalid start position */
+
+ /* The isolates[] entries contain enough information to
+ resume the bidi algorithm in the same state as it was
+ when it was interrupted by an isolate sequence. */
+ if (dirProps[aStart] == PDI && mIsolateCount >= 0) {
+ start1 = mIsolates[mIsolateCount].start1;
+ stateImp = mIsolates[mIsolateCount].stateImp;
+ levState.state = mIsolates[mIsolateCount].state;
+ mIsolateCount--;
+ } else {
+ levState.startON = -1;
+ start1 = aStart;
+ if (dirProps[aStart] == NSM) {
+ stateImp = 1 + aSOR;
+ } else {
+ stateImp = 0;
+ }
+ levState.state = 0;
+ ProcessPropertySeq(&levState, aSOR, aStart, aStart);
+ }
+ start2 = aStart;
+
+ for (i = aStart; i <= aLimit; i++) {
+ if (i >= aLimit) {
+ int32_t k;
+ for (k = aLimit - 1;
+ k > aStart && (DIRPROP_FLAG(dirProps[k]) & MASK_BN_EXPLICIT); k--) {
+ // empty loop body
+ }
+ dirProp = mDirProps[k];
+ if (dirProp == LRI || dirProp == RLI) {
+ break; /* no forced closing for sequence ending with LRI/RLI */
+ }
+ gprop = aEOR;
+ } else {
+ DirProp prop;
+ prop = dirProps[i];
+ gprop = groupProp[prop];
+ }
+ oldStateImp = stateImp;
+ cell = impTabProps[oldStateImp][gprop];
+ stateImp = GET_STATEPROPS(cell); /* isolate the new state */
+ actionImp = GET_ACTIONPROPS(cell); /* isolate the action */
+ if ((i == aLimit) && (actionImp == 0)) {
+ /* there is an unprocessed sequence if its property == eor */
+ actionImp = 1; /* process the last sequence */
+ }
+ if (actionImp) {
+ resProp = impTabProps[oldStateImp][IMPTABPROPS_RES];
+ switch (actionImp) {
+ case 1: /* process current seq1, init new seq1 */
+ ProcessPropertySeq(&levState, resProp, start1, i);
+ start1 = i;
+ break;
+ case 2: /* init new seq2 */
+ start2 = i;
+ break;
+ case 3: /* process seq1, process seq2, init new seq1 */
+ ProcessPropertySeq(&levState, resProp, start1, start2);
+ ProcessPropertySeq(&levState, DirProp_ON, start2, i);
+ start1 = i;
+ break;
+ case 4: /* process seq1, set seq1=seq2, init new seq2 */
+ ProcessPropertySeq(&levState, resProp, start1, start2);
+ start1 = start2;
+ start2 = i;
+ break;
+ default: /* we should never get here */
+ MOZ_ASSERT(false);
+ break;
+ }
+ }
+ }
+
+ for (i = aLimit - 1;
+ i > aStart && (DIRPROP_FLAG(dirProps[i]) & MASK_BN_EXPLICIT); i--) {
+ // empty loop body
+ }
+ dirProp = dirProps[i];
+ if ((dirProp == LRI || dirProp == RLI) && aLimit < mLength) {
+ mIsolateCount++;
+ mIsolates[mIsolateCount].stateImp = stateImp;
+ mIsolates[mIsolateCount].state = levState.state;
+ mIsolates[mIsolateCount].start1 = start1;
+ } else {
+ ProcessPropertySeq(&levState, aEOR, aLimit, aLimit);
+ }
+}
+
+
+/* perform (L1) and (X9) ---------------------------------------------------- */
+
+/*
+ * Reset the embedding levels for some non-graphic characters (L1).
+ * This function also sets appropriate levels for BN, and
+ * explicit embedding types that are supposed to have been removed
+ * from the paragraph in (X9).
+ */
+void nsBidi::AdjustWSLevels()
+{
+ const DirProp *dirProps=mDirProps;
+ nsBidiLevel *levels=mLevels;
+ int32_t i;
+
+ if(mFlags&MASK_WS) {
+ nsBidiLevel paraLevel=mParaLevel;
+ Flags flag;
+
+ i=mTrailingWSStart;
+ while(i>0) {
+ /* reset a sequence of WS/BN before eop and B/S to the paragraph paraLevel */
+ while (i > 0 && DIRPROP_FLAG(dirProps[--i]) & MASK_WS) {
+ levels[i]=paraLevel;
+ }
+
+ /* reset BN to the next character's paraLevel until B/S, which restarts above loop */
+ /* here, i+1 is guaranteed to be <length */
+ while(i>0) {
+ flag = DIRPROP_FLAG(dirProps[--i]);
+ if(flag&MASK_BN_EXPLICIT) {
+ levels[i]=levels[i+1];
+ } else if(flag&MASK_B_S) {
+ levels[i]=paraLevel;
+ break;
+ }
+ }
+ }
+ }
+}
+
+nsresult nsBidi::GetDirection(nsBidiDirection* aDirection)
+{
+ *aDirection = mDirection;
+ return NS_OK;
+}
+
+nsresult nsBidi::GetParaLevel(nsBidiLevel* aParaLevel)
+{
+ *aParaLevel = mParaLevel;
+ return NS_OK;
+}
+
+nsresult nsBidi::GetLogicalRun(int32_t aLogicalStart, int32_t *aLogicalLimit, nsBidiLevel *aLevel)
+{
+ int32_t length = mLength;
+
+ if(aLogicalStart<0 || length<=aLogicalStart) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t runCount, visualStart, logicalLimit, logicalFirst, i;
+ Run iRun;
+
+ /* CountRuns will check VALID_PARA_OR_LINE */
+ nsresult rv = CountRuns(&runCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ visualStart = logicalLimit = 0;
+ iRun = mRuns[0];
+
+ for (i = 0; i < runCount; i++) {
+ iRun = mRuns[i];
+ logicalFirst = GET_INDEX(iRun.logicalStart);
+ logicalLimit = logicalFirst + iRun.visualLimit - visualStart;
+ if ((aLogicalStart >= logicalFirst) && (aLogicalStart < logicalLimit)) {
+ break;
+ }
+ visualStart = iRun.visualLimit;
+ }
+ if (aLogicalLimit) {
+ *aLogicalLimit = logicalLimit;
+ }
+ if (aLevel) {
+ if (mDirection != NSBIDI_MIXED || aLogicalStart >= mTrailingWSStart) {
+ *aLevel = mParaLevel;
+ } else {
+ *aLevel = mLevels[aLogicalStart];
+ }
+ }
+ return NS_OK;
+}
+
+/* runs API functions ------------------------------------------------------- */
+
+nsresult nsBidi::CountRuns(int32_t* aRunCount)
+{
+ if(mRunCount<0 && !GetRuns()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ if (aRunCount)
+ *aRunCount = mRunCount;
+ return NS_OK;
+ }
+}
+
+nsresult nsBidi::GetVisualRun(int32_t aRunIndex, int32_t *aLogicalStart, int32_t *aLength, nsBidiDirection *aDirection)
+{
+ if( aRunIndex<0 ||
+ (mRunCount==-1 && !GetRuns()) ||
+ aRunIndex>=mRunCount
+ ) {
+ *aDirection = NSBIDI_LTR;
+ return NS_OK;
+ } else {
+ int32_t start=mRuns[aRunIndex].logicalStart;
+ if(aLogicalStart!=nullptr) {
+ *aLogicalStart=GET_INDEX(start);
+ }
+ if(aLength!=nullptr) {
+ if(aRunIndex>0) {
+ *aLength=mRuns[aRunIndex].visualLimit-
+ mRuns[aRunIndex-1].visualLimit;
+ } else {
+ *aLength=mRuns[0].visualLimit;
+ }
+ }
+ *aDirection = (nsBidiDirection)GET_ODD_BIT(start);
+ return NS_OK;
+ }
+}
+
+/* compute the runs array --------------------------------------------------- */
+
+/*
+ * Compute the runs array from the levels array.
+ * After GetRuns() returns true, runCount is guaranteed to be >0
+ * and the runs are reordered.
+ * Odd-level runs have visualStart on their visual right edge and
+ * they progress visually to the left.
+ */
+bool nsBidi::GetRuns()
+{
+ /*
+ * This method returns immediately if the runs are already set. This
+ * includes the case of length==0 (handled in setPara)..
+ */
+ if (mRunCount >= 0) {
+ return true;
+ }
+
+ if(mDirection!=NSBIDI_MIXED) {
+ /* simple, single-run case - this covers length==0 */
+ GetSingleRun(mParaLevel);
+ } else /* NSBIDI_MIXED, length>0 */ {
+ /* mixed directionality */
+ int32_t length=mLength, limit=mTrailingWSStart;
+
+ /*
+ * If there are WS characters at the end of the line
+ * and the run preceding them has a level different from
+ * paraLevel, then they will form their own run at paraLevel (L1).
+ * Count them separately.
+ * We need some special treatment for this in order to not
+ * modify the levels array which a line nsBidi object shares
+ * with its paragraph parent and its other line siblings.
+ * In other words, for the trailing WS, it may be
+ * levels[]!=paraLevel but we have to treat it like it were so.
+ */
+ nsBidiLevel *levels=mLevels;
+ int32_t i, runCount;
+ nsBidiLevel level=NSBIDI_DEFAULT_LTR; /* initialize with no valid level */
+
+ /* count the runs, there is at least one non-WS run, and limit>0 */
+ runCount=0;
+ for(i=0; i<limit; ++i) {
+ /* increment runCount at the start of each run */
+ if(levels[i]!=level) {
+ ++runCount;
+ level=levels[i];
+ }
+ }
+
+ /*
+ * We don't need to see if the last run can be merged with a trailing
+ * WS run because SetTrailingWSStart() would have done that.
+ */
+ if(runCount==1 && limit==length) {
+ /* There is only one non-WS run and no trailing WS-run. */
+ GetSingleRun(levels[0]);
+ } else /* runCount>1 || limit<length */ {
+ /* allocate and set the runs */
+ Run *runs;
+ int32_t runIndex, start;
+ nsBidiLevel minLevel=NSBIDI_MAX_EXPLICIT_LEVEL+1, maxLevel=0;
+
+ /* now, count a (non-mergable) WS run */
+ if(limit<length) {
+ ++runCount;
+ }
+
+ /* runCount>1 */
+ if(GETRUNSMEMORY(runCount)) {
+ runs=mRunsMemory;
+ } else {
+ return false;
+ }
+
+ /* set the runs */
+ /* this could be optimized, e.g.: 464->444, 484->444, 575->555, 595->555 */
+ /* however, that would take longer and make other functions more complicated */
+ runIndex=0;
+
+ /* search for the run ends */
+ i = 0;
+ do {
+ /* prepare this run */
+ start = i;
+ level = levels[i];
+ if(level<minLevel) {
+ minLevel=level;
+ }
+ if(level>maxLevel) {
+ maxLevel=level;
+ }
+
+ /* look for the run limit */
+ while (++i < limit && levels[i] == level) {
+ }
+
+ /* i is another run limit */
+ runs[runIndex].logicalStart = start;
+ runs[runIndex].visualLimit = i - start;
+ ++runIndex;
+ } while (i < limit);
+
+ if(limit<length) {
+ /* there is a separate WS run */
+ runs[runIndex].logicalStart=limit;
+ runs[runIndex].visualLimit=length-limit;
+ if(mParaLevel<minLevel) {
+ minLevel=mParaLevel;
+ }
+ }
+
+ /* set the object fields */
+ mRuns=runs;
+ mRunCount=runCount;
+
+ ReorderLine(minLevel, maxLevel);
+
+ /* now add the direction flags and adjust the visualLimit's to be just that */
+ /* this loop will also handling the trailing WS run */
+ limit = 0;
+ for (i = 0; i < runCount; ++i) {
+ ADD_ODD_BIT_FROM_LEVEL(runs[i].logicalStart, levels[runs[i].logicalStart]);
+ limit += runs[i].visualLimit;
+ runs[i].visualLimit = limit;
+ }
+
+ /* Set the "odd" bit for the trailing WS run. */
+ /* For a RTL paragraph, it will be the *first* run in visual order. */
+ if (runIndex < runCount) {
+ int32_t trailingRun = (mParaLevel & 1) ? 0 : runIndex;
+ ADD_ODD_BIT_FROM_LEVEL(runs[trailingRun].logicalStart, mParaLevel);
+ }
+ }
+ }
+
+ return true;
+}
+
+/* in trivial cases there is only one trivial run; called by GetRuns() */
+void nsBidi::GetSingleRun(nsBidiLevel aLevel)
+{
+ /* simple, single-run case */
+ mRuns=mSimpleRuns;
+ mRunCount=1;
+
+ /* fill and reorder the single run */
+ mRuns[0].logicalStart=MAKE_INDEX_ODD_PAIR(0, aLevel);
+ mRuns[0].visualLimit=mLength;
+}
+
+/* reorder the runs array (L2) ---------------------------------------------- */
+
+/*
+ * Reorder the same-level runs in the runs array.
+ * Here, runCount>1 and maxLevel>=minLevel>=paraLevel.
+ * All the visualStart fields=logical start before reordering.
+ * The "odd" bits are not set yet.
+ *
+ * Reordering with this data structure lends itself to some handy shortcuts:
+ *
+ * Since each run is moved but not modified, and since at the initial maxLevel
+ * each sequence of same-level runs consists of only one run each, we
+ * don't need to do anything there and can predecrement maxLevel.
+ * In many simple cases, the reordering is thus done entirely in the
+ * index mapping.
+ * Also, reordering occurs only down to the lowest odd level that occurs,
+ * which is minLevel|1. However, if the lowest level itself is odd, then
+ * in the last reordering the sequence of the runs at this level or higher
+ * will be all runs, and we don't need the elaborate loop to search for them.
+ * This is covered by ++minLevel instead of minLevel|=1 followed
+ * by an extra reorder-all after the reorder-some loop.
+ * About a trailing WS run:
+ * Such a run would need special treatment because its level is not
+ * reflected in levels[] if this is not a paragraph object.
+ * Instead, all characters from trailingWSStart on are implicitly at
+ * paraLevel.
+ * However, for all maxLevel>paraLevel, this run will never be reordered
+ * and does not need to be taken into account. maxLevel==paraLevel is only reordered
+ * if minLevel==paraLevel is odd, which is done in the extra segment.
+ * This means that for the main reordering loop we don't need to consider
+ * this run and can --runCount. If it is later part of the all-runs
+ * reordering, then runCount is adjusted accordingly.
+ */
+void nsBidi::ReorderLine(nsBidiLevel aMinLevel, nsBidiLevel aMaxLevel)
+{
+ Run *runs, tempRun;
+ nsBidiLevel *levels;
+ int32_t firstRun, endRun, limitRun, runCount;
+
+ /* nothing to do? */
+ if(aMaxLevel<=(aMinLevel|1)) {
+ return;
+ }
+
+ /*
+ * Reorder only down to the lowest odd level
+ * and reorder at an odd aMinLevel in a separate, simpler loop.
+ * See comments above for why aMinLevel is always incremented.
+ */
+ ++aMinLevel;
+
+ runs=mRuns;
+ levels=mLevels;
+ runCount=mRunCount;
+
+ /* do not include the WS run at paraLevel<=old aMinLevel except in the simple loop */
+ if(mTrailingWSStart<mLength) {
+ --runCount;
+ }
+
+ while(--aMaxLevel>=aMinLevel) {
+ firstRun=0;
+
+ /* loop for all sequences of runs */
+ for(;;) {
+ /* look for a sequence of runs that are all at >=aMaxLevel */
+ /* look for the first run of such a sequence */
+ while(firstRun<runCount && levels[runs[firstRun].logicalStart]<aMaxLevel) {
+ ++firstRun;
+ }
+ if(firstRun>=runCount) {
+ break; /* no more such runs */
+ }
+
+ /* look for the limit run of such a sequence (the run behind it) */
+ for(limitRun=firstRun; ++limitRun<runCount && levels[runs[limitRun].logicalStart]>=aMaxLevel;) {}
+
+ /* Swap the entire sequence of runs from firstRun to limitRun-1. */
+ endRun=limitRun-1;
+ while(firstRun<endRun) {
+ tempRun = runs[firstRun];
+ runs[firstRun] = runs[endRun];
+ runs[endRun] = tempRun;
+ ++firstRun;
+ --endRun;
+ }
+
+ if(limitRun==runCount) {
+ break; /* no more such runs */
+ } else {
+ firstRun=limitRun+1;
+ }
+ }
+ }
+
+ /* now do aMaxLevel==old aMinLevel (==odd!), see above */
+ if(!(aMinLevel&1)) {
+ firstRun=0;
+
+ /* include the trailing WS run in this complete reordering */
+ if(mTrailingWSStart==mLength) {
+ --runCount;
+ }
+
+ /* Swap the entire sequence of all runs. (endRun==runCount) */
+ while(firstRun<runCount) {
+ tempRun = runs[firstRun];
+ runs[firstRun] = runs[runCount];
+ runs[runCount] = tempRun;
+ ++firstRun;
+ --runCount;
+ }
+ }
+}
+
+nsresult nsBidi::ReorderVisual(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap)
+{
+ int32_t start, end, limit, temp;
+ nsBidiLevel minLevel, maxLevel;
+
+ if(aIndexMap==nullptr ||
+ !PrepareReorder(aLevels, aLength, aIndexMap, &minLevel, &maxLevel)) {
+ return NS_OK;
+ }
+
+ /* nothing to do? */
+ if(minLevel==maxLevel && (minLevel&1)==0) {
+ return NS_OK;
+ }
+
+ /* reorder only down to the lowest odd level */
+ minLevel|=1;
+
+ /* loop maxLevel..minLevel */
+ do {
+ start=0;
+
+ /* loop for all sequences of levels to reorder at the current maxLevel */
+ for(;;) {
+ /* look for a sequence of levels that are all at >=maxLevel */
+ /* look for the first index of such a sequence */
+ while(start<aLength && aLevels[start]<maxLevel) {
+ ++start;
+ }
+ if(start>=aLength) {
+ break; /* no more such runs */
+ }
+
+ /* look for the limit of such a sequence (the index behind it) */
+ for(limit=start; ++limit<aLength && aLevels[limit]>=maxLevel;) {}
+
+ /*
+ * Swap the entire interval of indexes from start to limit-1.
+ * We don't need to swap the levels for the purpose of this
+ * algorithm: the sequence of levels that we look at does not
+ * move anyway.
+ */
+ end=limit-1;
+ while(start<end) {
+ temp=aIndexMap[start];
+ aIndexMap[start]=aIndexMap[end];
+ aIndexMap[end]=temp;
+
+ ++start;
+ --end;
+ }
+
+ if(limit==aLength) {
+ break; /* no more such sequences */
+ } else {
+ start=limit+1;
+ }
+ }
+ } while(--maxLevel>=minLevel);
+
+ return NS_OK;
+}
+
+bool nsBidi::PrepareReorder(const nsBidiLevel *aLevels, int32_t aLength,
+ int32_t *aIndexMap,
+ nsBidiLevel *aMinLevel, nsBidiLevel *aMaxLevel)
+{
+ int32_t start;
+ nsBidiLevel level, minLevel, maxLevel;
+
+ if(aLevels==nullptr || aLength<=0) {
+ return false;
+ }
+
+ /* determine minLevel and maxLevel */
+ minLevel=NSBIDI_MAX_EXPLICIT_LEVEL+1;
+ maxLevel=0;
+ for(start=aLength; start>0;) {
+ level=aLevels[--start];
+ if(level>NSBIDI_MAX_EXPLICIT_LEVEL+1) {
+ return false;
+ }
+ if(level<minLevel) {
+ minLevel=level;
+ }
+ if(level>maxLevel) {
+ maxLevel=level;
+ }
+ }
+ *aMinLevel=minLevel;
+ *aMaxLevel=maxLevel;
+
+ /* initialize the index map */
+ for(start=aLength; start>0;) {
+ --start;
+ aIndexMap[start]=start;
+ }
+
+ return true;
+}
diff --git a/layout/base/nsBidi_noICU.h b/layout/base/nsBidi_noICU.h
new file mode 100644
index 000000000..a3f5aef1c
--- /dev/null
+++ b/layout/base/nsBidi_noICU.h
@@ -0,0 +1,709 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidi_noICU_h__
+#define nsBidi_noICU_h__
+
+#include "nsBidiUtils.h"
+#include "nsIFrame.h" // for frame property declaration
+
+// Bidi reordering engine from ICU
+/*
+ * javadoc-style comments are intended to be transformed into HTML
+ * using DOC++ - see
+ * http://www.zib.de/Visual/software/doc++/index.html .
+ *
+ * The HTML documentation is created with
+ * doc++ -H nsBidi.h
+ */
+
+/**
+ * @mainpage BIDI algorithm for Mozilla (from ICU)
+ *
+ * <h2>BIDI algorithm for Mozilla</h2>
+ *
+ * This is an implementation of the Unicode Bidirectional algorithm.
+ * The algorithm is defined in the
+ * <a href="http://www.unicode.org/unicode/reports/tr9/">Unicode Technical Report 9</a>,
+ * version 5, also described in The Unicode Standard, Version 3.0 .<p>
+ *
+ * <h3>General remarks about the API:</h3>
+ *
+ * The <quote>limit</quote> of a sequence of characters is the position just after their
+ * last character, i.e., one more than that position.<p>
+ *
+ * Some of the API functions provide access to <quote>runs</quote>.
+ * Such a <quote>run</quote> is defined as a sequence of characters
+ * that are at the same embedding level
+ * after performing the BIDI algorithm.<p>
+ *
+ * @author Markus W. Scherer. Ported to Mozilla by Simon Montagu
+ * @version 1.0
+ */
+
+/**
+ * Special value which can be returned by the mapping functions when a logical
+ * index has no corresponding visual index or vice-versa.
+ * @see GetVisualIndex
+ * @see GetVisualMap
+ * @see GetLogicalIndex
+ * @see GetLogicalMap
+ */
+#define NSBIDI_MAP_NOWHERE (-1)
+
+/* miscellaneous definitions ------------------------------------------------ */
+
+/* helper macros for each allocated array member */
+#define GETDIRPROPSMEMORY(length) nsBidi::GetMemory((void **)&mDirPropsMemory, \
+ &mDirPropsSize, \
+ (length))
+
+#define GETLEVELSMEMORY(length) nsBidi::GetMemory((void **)&mLevelsMemory, \
+ &mLevelsSize, \
+ (length))
+
+#define GETRUNSMEMORY(length) nsBidi::GetMemory((void **)&mRunsMemory, \
+ &mRunsSize, \
+ (length)*sizeof(Run))
+
+#define GETISOLATESMEMORY(length) nsBidi::GetMemory((void **)&mIsolatesMemory, \
+ &mIsolatesSize, \
+ (length)*sizeof(Isolate))
+
+#define GETOPENINGSMEMORY(length) nsBidi::GetMemory((void **)&mOpeningsMemory, \
+ &mOpeningsSize, \
+ (length)*sizeof(Opening))
+
+/*
+ * Sometimes, bit values are more appropriate
+ * to deal with directionality properties.
+ * Abbreviations in these macro names refer to names
+ * used in the Bidi algorithm.
+ */
+typedef uint8_t DirProp;
+
+#define DIRPROP_FLAG(dir) (1UL<<(dir))
+
+/* special flag for multiple runs from explicit embedding codes */
+#define DIRPROP_FLAG_MULTI_RUNS (1UL<<31)
+
+/* are there any characters that are LTR or RTL? */
+#define MASK_LTR (DIRPROP_FLAG(L)|DIRPROP_FLAG(EN)|DIRPROP_FLAG(ENL)| \
+ DIRPROP_FLAG(ENR)|DIRPROP_FLAG(AN)|DIRPROP_FLAG(LRE)| \
+ DIRPROP_FLAG(LRO)|DIRPROP_FLAG(LRI))
+#define MASK_RTL (DIRPROP_FLAG(R)|DIRPROP_FLAG(AL)|DIRPROP_FLAG(RLE)| \
+ DIRPROP_FLAG(RLO)|DIRPROP_FLAG(RLI))
+#define MASK_R_AL (DIRPROP_FLAG(R)|DIRPROP_FLAG(AL))
+
+/* explicit embedding codes */
+#define MASK_EXPLICIT (DIRPROP_FLAG(LRE)|DIRPROP_FLAG(LRO)|DIRPROP_FLAG(RLE)|DIRPROP_FLAG(RLO)|DIRPROP_FLAG(PDF))
+
+/* explicit isolate codes */
+#define MASK_ISO (DIRPROP_FLAG(LRI)|DIRPROP_FLAG(RLI)|DIRPROP_FLAG(FSI)|DIRPROP_FLAG(PDI))
+
+#define MASK_BN_EXPLICIT (DIRPROP_FLAG(BN)|MASK_EXPLICIT)
+
+/* paragraph and segment separators */
+#define MASK_B_S (DIRPROP_FLAG(B)|DIRPROP_FLAG(S))
+
+/* all types that are counted as White Space or Neutral in some steps */
+#define MASK_WS (MASK_B_S|DIRPROP_FLAG(WS)|MASK_BN_EXPLICIT|MASK_ISO)
+
+/* types that are neutrals or could becomes neutrals in (Wn) */
+#define MASK_POSSIBLE_N (DIRPROP_FLAG(O_N)|DIRPROP_FLAG(CS)|DIRPROP_FLAG(ES)|DIRPROP_FLAG(ET)|MASK_WS)
+
+/*
+ * These types may be changed to "e",
+ * the embedding type (L or R) of the run,
+ * in the Bidi algorithm (N2)
+ */
+#define MASK_EMBEDDING (DIRPROP_FLAG(NSM)|MASK_POSSIBLE_N)
+
+/* the dirProp's L and R are defined to 0 and 1 values in nsCharType */
+#define GET_LR_FROM_LEVEL(level) ((DirProp)((level)&1))
+
+#define IS_DEFAULT_LEVEL(level) (((level)&0xfe)==0xfe)
+
+/*
+ * The following bit is used for the directional isolate status.
+ * Stack entries corresponding to isolate sequences are greater than ISOLATE.
+ */
+#define ISOLATE 0x0100
+
+/* number of isolate entries allocated initially without malloc */
+#define SIMPLE_ISOLATES_SIZE 5
+
+/* number of isolate run entries for paired brackets allocated initially without malloc */
+#define SIMPLE_OPENINGS_COUNT 8
+
+/* handle surrogate pairs --------------------------------------------------- */
+
+#define IS_FIRST_SURROGATE(uchar) (((uchar)&0xfc00)==0xd800)
+#define IS_SECOND_SURROGATE(uchar) (((uchar)&0xfc00)==0xdc00)
+
+/* get the UTF-32 value directly from the surrogate pseudo-characters */
+#define SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000)
+#define GET_UTF_32(first, second) (((first)<<10UL)+(second)-SURROGATE_OFFSET)
+
+#if !ENABLE_INTL_API // these are provided by ICU if present in the build
+
+#define UTF_ERROR_VALUE 0xffff
+/* definitions with forward iteration --------------------------------------- */
+
+/*
+ * all the macros that go forward assume that
+ * the initial offset is 0<=i<length;
+ * they update the offset
+ */
+
+/* fast versions, no error-checking */
+
+#define UTF16_APPEND_CHAR_UNSAFE(s, i, c){ \
+ if((uint32_t)(c)<=0xffff) { \
+ (s)[(i)++]=(char16_t)(c); \
+ } else { \
+ (s)[(i)++]=(char16_t)((c)>>10)+0xd7c0; \
+ (s)[(i)++]=(char16_t)(c)&0x3ff|0xdc00; \
+ } \
+}
+
+/* safe versions with error-checking and optional regularity-checking */
+
+#define UTF16_APPEND_CHAR_SAFE(s, i, length, c) { \
+ if((PRUInt32)(c)<=0xffff) { \
+ (s)[(i)++]=(char16_t)(c); \
+ } else if((PRUInt32)(c)<=0x10ffff) { \
+ if((i)+1<(length)) { \
+ (s)[(i)++]=(char16_t)((c)>>10)+0xd7c0; \
+ (s)[(i)++]=(char16_t)(c)&0x3ff|0xdc00; \
+ } else /* not enough space */ { \
+ (s)[(i)++]=UTF_ERROR_VALUE; \
+ } \
+ } else /* c>0x10ffff, write error value */ { \
+ (s)[(i)++]=UTF_ERROR_VALUE; \
+ } \
+}
+
+/* definitions with backward iteration -------------------------------------- */
+
+/*
+ * all the macros that go backward assume that
+ * the valid buffer range starts at offset 0
+ * and that the initial offset is 0<i<=length;
+ * they update the offset
+ */
+
+/* fast versions, no error-checking */
+
+/*
+ * Get a single code point from an offset that points behind the last
+ * of the code units that belong to that code point.
+ * Assume 0<=i<length.
+ */
+#define UTF16_PREV_CHAR_UNSAFE(s, i, c) { \
+ (c)=(s)[--(i)]; \
+ if(IS_SECOND_SURROGATE(c)) { \
+ (c)=GET_UTF_32((s)[--(i)], (c)); \
+ } \
+}
+
+#define UTF16_BACK_1_UNSAFE(s, i) { \
+ if(IS_SECOND_SURROGATE((s)[--(i)])) { \
+ --(i); \
+ } \
+}
+
+#define UTF16_BACK_N_UNSAFE(s, i, n) { \
+ int32_t __N=(n); \
+ while(__N>0) { \
+ UTF16_BACK_1_UNSAFE(s, i); \
+ --__N; \
+ } \
+}
+
+/* safe versions with error-checking and optional regularity-checking */
+
+#define UTF16_PREV_CHAR_SAFE(s, start, i, c, strict) { \
+ (c)=(s)[--(i)]; \
+ if(IS_SECOND_SURROGATE(c)) { \
+ char16_t __c2; \
+ if((i)>(start) && IS_FIRST_SURROGATE(__c2=(s)[(i)-1])) { \
+ --(i); \
+ (c)=GET_UTF_32(__c2, (c)); \
+ /* strict: ((c)&0xfffe)==0xfffe is caught by UTF_IS_ERROR() */ \
+ } else if(strict) {\
+ /* unmatched second surrogate */ \
+ (c)=UTF_ERROR_VALUE; \
+ } \
+ } else if(strict && IS_FIRST_SURROGATE(c)) { \
+ /* unmatched first surrogate */ \
+ (c)=UTF_ERROR_VALUE; \
+ /* else strict: (c)==0xfffe is caught by UTF_IS_ERROR() */ \
+ } \
+}
+
+#define UTF16_BACK_1_SAFE(s, start, i) { \
+ if(IS_SECOND_SURROGATE((s)[--(i)]) && (i)>(start) && IS_FIRST_SURROGATE((s)[(i)-1])) { \
+ --(i); \
+ } \
+}
+
+#define UTF16_BACK_N_SAFE(s, start, i, n) { \
+ int32_t __N=(n); \
+ while(__N>0 && (i)>(start)) { \
+ UTF16_BACK_1_SAFE(s, start, i); \
+ --__N; \
+ } \
+}
+
+#define UTF_PREV_CHAR_UNSAFE(s, i, c) UTF16_PREV_CHAR_UNSAFE(s, i, c)
+#define UTF_PREV_CHAR_SAFE(s, start, i, c, strict) UTF16_PREV_CHAR_SAFE(s, start, i, c, strict)
+#define UTF_BACK_1_UNSAFE(s, i) UTF16_BACK_1_UNSAFE(s, i)
+#define UTF_BACK_1_SAFE(s, start, i) UTF16_BACK_1_SAFE(s, start, i)
+#define UTF_BACK_N_UNSAFE(s, i, n) UTF16_BACK_N_UNSAFE(s, i, n)
+#define UTF_BACK_N_SAFE(s, start, i, n) UTF16_BACK_N_SAFE(s, start, i, n)
+#define UTF_APPEND_CHAR_UNSAFE(s, i, c) UTF16_APPEND_CHAR_UNSAFE(s, i, c)
+#define UTF_APPEND_CHAR_SAFE(s, i, length, c) UTF16_APPEND_CHAR_SAFE(s, i, length, c)
+
+#define UTF_PREV_CHAR(s, start, i, c) UTF_PREV_CHAR_SAFE(s, start, i, c, false)
+#define UTF_BACK_1(s, start, i) UTF_BACK_1_SAFE(s, start, i)
+#define UTF_BACK_N(s, start, i, n) UTF_BACK_N_SAFE(s, start, i, n)
+#define UTF_APPEND_CHAR(s, i, length, c) UTF_APPEND_CHAR_SAFE(s, i, length, c)
+
+#endif // !ENABLE_INTL_API
+
+struct Isolate {
+ int32_t start1;
+ int16_t stateImp;
+ int16_t state;
+};
+
+// For bracket matching
+
+#define FOUND_L DIRPROP_FLAG(L)
+#define FOUND_R DIRPROP_FLAG(R)
+
+struct Opening {
+ int32_t position; /* position of opening bracket */
+ int32_t match; /* matching char or -position of closing bracket */
+ int32_t contextPos; /* position of last strong char found before opening */
+ uint16_t flags; /* bits for L or R/AL found within the pair */
+ DirProp contextDir; /* L or R according to last strong char before opening */
+ uint8_t filler; /* to complete a nice multiple of 4 chars */
+};
+
+struct IsoRun {
+ int32_t contextPos; /* position of char determining context */
+ uint16_t start; /* index of first opening entry for this run */
+ uint16_t limit; /* index after last opening entry for this run */
+ nsBidiLevel level; /* level of this run */
+ DirProp lastStrong; /* bidi class of last strong char found in this run */
+ DirProp lastBase; /* bidi class of last base char found in this run */
+ DirProp contextDir; /* L or R to use as context for following openings */
+};
+
+class nsBidi;
+
+/* Run structure for reordering --------------------------------------------- */
+
+typedef struct Run {
+ int32_t logicalStart; /* first character of the run; b31 indicates even/odd level */
+ int32_t visualLimit; /* last visual position of the run +1 */
+} Run;
+
+/* in a Run, logicalStart will get this bit set if the run level is odd */
+#define INDEX_ODD_BIT (1UL<<31)
+
+#define MAKE_INDEX_ODD_PAIR(index, level) (index|((uint32_t)level<<31))
+#define ADD_ODD_BIT_FROM_LEVEL(x, level) ((x)|=((uint32_t)level<<31))
+#define REMOVE_ODD_BIT(x) ((x)&=~INDEX_ODD_BIT)
+
+#define GET_INDEX(x) ((x)&~INDEX_ODD_BIT)
+#define GET_ODD_BIT(x) ((uint32_t)(x)>>31)
+#define IS_ODD_RUN(x) (((x)&INDEX_ODD_BIT)!=0)
+#define IS_EVEN_RUN(x) (((x)&INDEX_ODD_BIT)==0)
+
+typedef uint32_t Flags;
+
+enum { DirProp_L=0, DirProp_R=1, DirProp_EN=2, DirProp_AN=3, DirProp_ON=4, DirProp_S=5, DirProp_B=6 }; /* reduced dirProp */
+
+#define IMPTABLEVELS_COLUMNS (DirProp_B + 2)
+typedef const uint8_t ImpTab[][IMPTABLEVELS_COLUMNS];
+typedef const uint8_t (*PImpTab)[IMPTABLEVELS_COLUMNS];
+
+typedef const uint8_t ImpAct[];
+typedef const uint8_t *PImpAct;
+
+struct LevState {
+ PImpTab pImpTab; /* level table pointer */
+ PImpAct pImpAct; /* action map array */
+ int32_t startON; /* start of ON sequence */
+ int32_t state; /* current state */
+ int32_t runStart; /* start position of the run */
+ nsBidiLevel runLevel; /* run level before implicit solving */
+};
+
+/**
+ * This class holds information about a paragraph of text
+ * with Bidi-algorithm-related details, or about one line of
+ * such a paragraph.<p>
+ * Reordering can be done on a line, or on a paragraph which is
+ * then interpreted as one single line.<p>
+ *
+ * On construction, the class is initially empty. It is assigned
+ * the Bidi properties of a paragraph by <code>SetPara</code>
+ * or the Bidi properties of a line of a paragraph by
+ * <code>SetLine</code>.<p>
+ * A Bidi class can be reused for as long as it is not deallocated
+ * by calling its destructor.<p>
+ * <code>SetPara</code> will allocate additional memory for
+ * internal structures as necessary.
+ */
+class nsBidi
+{
+public:
+ /** @brief Default constructor.
+ *
+ * The nsBidi object is initially empty. It is assigned
+ * the Bidi properties of a paragraph by <code>SetPara()</code>
+ * or the Bidi properties of a line of a paragraph by
+ * <code>GetLine()</code>.<p>
+ * This object can be reused for as long as it is not destroyed.<p>
+ * <code>SetPara()</code> will allocate additional memory for
+ * internal structures as necessary.
+ *
+ */
+ nsBidi();
+
+ /** @brief Destructor. */
+ virtual ~nsBidi();
+
+
+ /**
+ * Perform the Unicode Bidi algorithm. It is defined in the
+ * <a href="http://www.unicode.org/unicode/reports/tr9/">Unicode Technical Report 9</a>,
+ * version 5,
+ * also described in The Unicode Standard, Version 3.0 .<p>
+ *
+ * This function takes a single plain text paragraph with or without
+ * externally specified embedding levels from <quote>styled</quote> text
+ * and computes the left-right-directionality of each character.<p>
+ *
+ * If the entire paragraph consists of text of only one direction, then
+ * the function may not perform all the steps described by the algorithm,
+ * i.e., some levels may not be the same as if all steps were performed.
+ * This is not relevant for unidirectional text.<br>
+ * For example, in pure LTR text with numbers the numbers would get
+ * a resolved level of 2 higher than the surrounding text according to
+ * the algorithm. This implementation may set all resolved levels to
+ * the same value in such a case.<p>
+ *
+ * The text must be externally split into separate paragraphs (rule P1).
+ * Paragraph separators (B) should appear at most at the very end.
+ *
+ * @param aText is a pointer to the single-paragraph text that the
+ * Bidi algorithm will be performed on
+ * (step (P1) of the algorithm is performed externally).
+ * <strong>The text must be (at least) <code>aLength</code> long.</strong>
+ *
+ * @param aLength is the length of the text; if <code>aLength==-1</code> then
+ * the text must be zero-terminated.
+ *
+ * @param aParaLevel specifies the default level for the paragraph;
+ * it is typically 0 (LTR) or 1 (RTL).
+ * If the function shall determine the paragraph level from the text,
+ * then <code>aParaLevel</code> can be set to
+ * either <code>NSBIDI_DEFAULT_LTR</code>
+ * or <code>NSBIDI_DEFAULT_RTL</code>;
+ * if there is no strongly typed character, then
+ * the desired default is used (0 for LTR or 1 for RTL).
+ * Any other value between 0 and <code>NSBIDI_MAX_EXPLICIT_LEVEL</code> is also valid,
+ * with odd levels indicating RTL.
+ */
+ nsresult SetPara(const char16_t *aText, int32_t aLength, nsBidiLevel aParaLevel);
+
+ /**
+ * Get the directionality of the text.
+ *
+ * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates if the entire text
+ * represented by this object is unidirectional,
+ * and which direction, or if it is mixed-directional.
+ *
+ * @see nsBidiDirection
+ */
+ nsresult GetDirection(nsBidiDirection* aDirection);
+
+ /**
+ * Get the paragraph level of the text.
+ *
+ * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating the paragraph level
+ *
+ * @see nsBidiLevel
+ */
+ nsresult GetParaLevel(nsBidiLevel* aParaLevel);
+
+ /**
+ * Get a logical run.
+ * This function returns information about a run and is used
+ * to retrieve runs in logical order.<p>
+ * This is especially useful for line-breaking on a paragraph.
+ *
+ * @param aLogicalStart is the first character of the run.
+ *
+ * @param aLogicalLimit will receive the limit of the run.
+ * The l-value that you point to here may be the
+ * same expression (variable) as the one for
+ * <code>aLogicalStart</code>.
+ * This pointer can be <code>nullptr</code> if this
+ * value is not necessary.
+ *
+ * @param aLevel will receive the level of the run.
+ * This pointer can be <code>nullptr</code> if this
+ * value is not necessary.
+ */
+ nsresult GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, nsBidiLevel* aLevel);
+
+ /**
+ * Get the number of runs.
+ * This function may invoke the actual reordering on the
+ * <code>nsBidi</code> object, after <code>SetPara</code>
+ * may have resolved only the levels of the text. Therefore,
+ * <code>CountRuns</code> may have to allocate memory,
+ * and may fail doing so.
+ *
+ * @param aRunCount will receive the number of runs.
+ */
+ nsresult CountRuns(int32_t* aRunCount);
+
+ /**
+ * Get one run's logical start, length, and directionality,
+ * which can be 0 for LTR or 1 for RTL.
+ * In an RTL run, the character at the logical start is
+ * visually on the right of the displayed run.
+ * The length is the number of characters in the run.<p>
+ * <code>CountRuns</code> should be called
+ * before the runs are retrieved.
+ *
+ * @param aRunIndex is the number of the run in visual order, in the
+ * range <code>[0..CountRuns-1]</code>.
+ *
+ * @param aLogicalStart is the first logical character index in the text.
+ * The pointer may be <code>nullptr</code> if this index is not needed.
+ *
+ * @param aLength is the number of characters (at least one) in the run.
+ * The pointer may be <code>nullptr</code> if this is not needed.
+ *
+ * @param aDirection will receive the directionality of the run,
+ * <code>NSBIDI_LTR==0</code> or <code>NSBIDI_RTL==1</code>,
+ * never <code>NSBIDI_MIXED</code>.
+ *
+ * @see CountRuns<p>
+ *
+ * Example:
+ * @code
+ * int32_t i, count, logicalStart, visualIndex=0, length;
+ * nsBidiDirection dir;
+ * pBidi->CountRuns(&count);
+ * for(i=0; i<count; ++i) {
+ * pBidi->GetVisualRun(i, &logicalStart, &length, &dir);
+ * if(NSBIDI_LTR==dir) {
+ * do { // LTR
+ * show_char(text[logicalStart++], visualIndex++);
+ * } while(--length>0);
+ * } else {
+ * logicalStart+=length; // logicalLimit
+ * do { // RTL
+ * show_char(text[--logicalStart], visualIndex++);
+ * } while(--length>0);
+ * }
+ * }
+ * @endcode
+ *
+ * Note that in right-to-left runs, code like this places
+ * modifier letters before base characters and second surrogates
+ * before first ones.
+ */
+ nsresult GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart, int32_t* aLength, nsBidiDirection* aDirection);
+
+ /**
+ * This is a convenience function that does not use a nsBidi object.
+ * It is intended to be used for when an application has determined the levels
+ * of objects (character sequences) and just needs to have them reordered (L2).
+ * This is equivalent to using <code>GetVisualMap</code> on a
+ * <code>nsBidi</code> object.
+ *
+ * @param aLevels is an array with <code>aLength</code> levels that have been determined by
+ * the application.
+ *
+ * @param aLength is the number of levels in the array, or, semantically,
+ * the number of objects to be reordered.
+ * It must be <code>aLength>0</code>.
+ *
+ * @param aIndexMap is a pointer to an array of <code>aLength</code>
+ * indexes which will reflect the reordering of the characters.
+ * The array does not need to be initialized.<p>
+ * The index map will result in <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>.
+ */
+ static nsresult ReorderVisual(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap);
+
+ /**
+ * Reverse a Right-To-Left run of Unicode text.
+ *
+ * This function preserves the integrity of characters with multiple
+ * code units and (optionally) modifier letters.
+ * Characters can be replaced by mirror-image characters
+ * in the destination buffer. Note that "real" mirroring has
+ * to be done in a rendering engine by glyph selection
+ * and that for many "mirrored" characters there are no
+ * Unicode characters as mirror-image equivalents.
+ * There are also options to insert or remove Bidi control
+ * characters; see the description of the <code>aDestSize</code>
+ * and <code>aOptions</code> parameters and of the option bit flags.
+ *
+ * Since no Bidi controls are inserted here, this function will never
+ * write more than <code>aSrcLength</code> characters to <code>aDest</code>.
+ *
+ * @param aSrc A pointer to the RTL run text.
+ *
+ * @param aSrcLength The length of the RTL run.
+ * If the <code>NSBIDI_REMOVE_BIDI_CONTROLS</code> option
+ * is set, then the destination length may be less than
+ * <code>aSrcLength</code>.
+ * If this option is not set, then the destination length
+ * will be exactly <code>aSrcLength</code>.
+ *
+ * @param aDest A pointer to where the reordered text is to be copied.
+ * <code>aSrc[aSrcLength]</code> and <code>aDest[aSrcLength]</code>
+ * must not overlap.
+ *
+ * @param aOptions A bit set of options for the reordering that control
+ * how the reordered text is written.
+ *
+ * @param aDestSize will receive the number of characters that were written to <code>aDest</code>.
+ */
+ nsresult WriteReverse(const char16_t *aSrc, int32_t aSrcLength, char16_t *aDest, uint16_t aOptions, int32_t *aDestSize);
+
+protected:
+ friend class nsBidiPresUtils;
+
+ class BracketData {
+ public:
+ explicit BracketData(const nsBidi* aBidi);
+ ~BracketData();
+
+ void ProcessBoundary(int32_t aLastDirControlCharPos,
+ nsBidiLevel aContextLevel,
+ nsBidiLevel aEmbeddingLevel,
+ const DirProp* aDirProps);
+ void ProcessLRI_RLI(nsBidiLevel aLevel);
+ void ProcessPDI();
+ bool AddOpening(char16_t aMatch, int32_t aPosition);
+ void FixN0c(int32_t aOpeningIndex, int32_t aNewPropPosition,
+ DirProp aNewProp, DirProp* aDirProps);
+ DirProp ProcessClosing(int32_t aOpenIdx, int32_t aPosition,
+ DirProp* aDirProps);
+ bool ProcessChar(int32_t aPosition, char16_t aCh, DirProp* aDirProps,
+ nsBidiLevel* aLevels);
+
+ private:
+ // array of opening entries which should be enough in most cases;
+ // no malloc() needed
+ Opening mSimpleOpenings[SIMPLE_OPENINGS_COUNT];
+ Opening* mOpenings; // pointer to current array of entries,
+ // either mSimpleOpenings or malloced array
+
+ Opening* mOpeningsMemory;
+ size_t mOpeningsSize;
+
+ // array of nested isolated sequence entries; can never exceed
+ // UBIDI_MAX_EXPLICIT_LEVEL
+ // + 1 for index 0
+ // + 1 for before the first isolated sequence
+ IsoRun mIsoRuns[NSBIDI_MAX_EXPLICIT_LEVEL+2];
+ int32_t mIsoRunLast; // index of last used entry in mIsoRuns
+
+ int32_t mOpeningsCount; // number of allocated entries in mOpenings
+ };
+
+ /** length of the current text */
+ int32_t mLength;
+
+ /** memory sizes in bytes */
+ size_t mDirPropsSize, mLevelsSize, mRunsSize;
+ size_t mIsolatesSize;
+
+ /** allocated memory */
+ DirProp* mDirPropsMemory;
+ nsBidiLevel* mLevelsMemory;
+ Run* mRunsMemory;
+ Isolate* mIsolatesMemory;
+
+ DirProp* mDirProps;
+ nsBidiLevel* mLevels;
+
+ /** the paragraph level */
+ nsBidiLevel mParaLevel;
+
+ /** flags is a bit set for which directional properties are in the text */
+ Flags mFlags;
+
+ /** the overall paragraph or line directionality - see nsBidiDirection */
+ nsBidiDirection mDirection;
+
+ /** characters after trailingWSStart are WS and are */
+ /* implicitly at the paraLevel (rule (L1)) - levels may not reflect that */
+ int32_t mTrailingWSStart;
+
+ /** fields for line reordering */
+ int32_t mRunCount; /* ==-1: runs not set up yet */
+ Run* mRuns;
+
+ /** for non-mixed text, we only need a tiny array of runs (no malloc()) */
+ Run mSimpleRuns[1];
+
+ /* maxium of current nesting depth of isolate sequences */
+ /* Within ResolveExplicitLevels() and checkExpicitLevels(), this is the maximal
+ nesting encountered.
+ Within ResolveImplicitLevels(), this is the index of the current isolates
+ stack entry. */
+ int32_t mIsolateCount;
+ Isolate* mIsolates;
+
+ /** for simple text, have a small stack (no malloc()) */
+ Isolate mSimpleIsolates[SIMPLE_ISOLATES_SIZE];
+
+private:
+
+ void Init();
+
+ static bool GetMemory(void **aMemory, size_t* aSize, size_t aSizeNeeded);
+
+ void Free();
+
+ void GetDirProps(const char16_t *aText);
+
+ void ResolveExplicitLevels(nsBidiDirection *aDirection, const char16_t *aText);
+
+ nsBidiDirection DirectionFromFlags(Flags aFlags);
+
+ void ProcessPropertySeq(LevState *pLevState, uint8_t _prop, int32_t start, int32_t limit);
+
+ void ResolveImplicitLevels(int32_t aStart, int32_t aLimit, DirProp aSOR, DirProp aEOR);
+
+ void AdjustWSLevels();
+
+ void SetTrailingWSStart();
+
+ bool GetRuns();
+
+ void GetSingleRun(nsBidiLevel aLevel);
+
+ void ReorderLine(nsBidiLevel aMinLevel, nsBidiLevel aMaxLevel);
+
+ static bool PrepareReorder(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap, nsBidiLevel *aMinLevel, nsBidiLevel *aMaxLevel);
+};
+
+#endif // _nsBidi_noICU_h_
diff --git a/layout/base/nsCSSColorUtils.cpp b/layout/base/nsCSSColorUtils.cpp
new file mode 100644
index 000000000..806b7c3cb
--- /dev/null
+++ b/layout/base/nsCSSColorUtils.cpp
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* functions that manipulate colors */
+
+#include "nsCSSColorUtils.h"
+#include "nsDebug.h"
+#include <math.h>
+
+// Weird color computing code stolen from winfe which was stolen
+// from the xfe which was written originally by Eric Bina. So there.
+
+#define RED_LUMINOSITY 299
+#define GREEN_LUMINOSITY 587
+#define BLUE_LUMINOSITY 114
+#define INTENSITY_FACTOR 25
+#define LUMINOSITY_FACTOR 75
+
+#define MAX_COLOR 255
+#define COLOR_DARK_THRESHOLD 51
+#define COLOR_LIGHT_THRESHOLD 204
+
+#define COLOR_LITE_BS_FACTOR 45
+#define COLOR_LITE_TS_FACTOR 70
+
+#define COLOR_DARK_BS_FACTOR 30
+#define COLOR_DARK_TS_FACTOR 50
+
+#define LIGHT_GRAY NS_RGB(192, 192, 192)
+#define DARK_GRAY NS_RGB(96, 96, 96)
+
+#define MAX_BRIGHTNESS 254
+#define MAX_DARKNESS 0
+
+void NS_GetSpecial3DColors(nscolor aResult[2],
+ nscolor aBackgroundColor,
+ nscolor aBorderColor)
+{
+
+ uint8_t f0, f1;
+ uint8_t r, g, b;
+
+ uint8_t rb = NS_GET_R(aBorderColor);
+ uint8_t gb = NS_GET_G(aBorderColor);
+ uint8_t bb = NS_GET_B(aBorderColor);
+
+ uint8_t a = NS_GET_A(aBorderColor);
+
+ // This needs to be optimized.
+ // Calculating background brightness again and again is
+ // a waste of time!!!. Just calculate it only once.
+ // .....somehow!!!
+
+ uint8_t red = NS_GET_R(aBackgroundColor);
+ uint8_t green = NS_GET_G(aBackgroundColor);
+ uint8_t blue = NS_GET_B(aBackgroundColor);
+
+ uint8_t elementBrightness = NS_GetBrightness(rb,gb,bb);
+ uint8_t backgroundBrightness = NS_GetBrightness(red, green, blue);
+
+
+ if (backgroundBrightness < COLOR_DARK_THRESHOLD) {
+ f0 = COLOR_DARK_BS_FACTOR;
+ f1 = COLOR_DARK_TS_FACTOR;
+ if(elementBrightness == MAX_DARKNESS)
+ {
+ rb = NS_GET_R(DARK_GRAY);
+ gb = NS_GET_G(DARK_GRAY);
+ bb = NS_GET_B(DARK_GRAY);
+ }
+ }else if (backgroundBrightness > COLOR_LIGHT_THRESHOLD) {
+ f0 = COLOR_LITE_BS_FACTOR;
+ f1 = COLOR_LITE_TS_FACTOR;
+ if(elementBrightness == MAX_BRIGHTNESS)
+ {
+ rb = NS_GET_R(LIGHT_GRAY);
+ gb = NS_GET_G(LIGHT_GRAY);
+ bb = NS_GET_B(LIGHT_GRAY);
+ }
+ }else {
+ f0 = COLOR_DARK_BS_FACTOR +
+ (backgroundBrightness *
+ (COLOR_LITE_BS_FACTOR - COLOR_DARK_BS_FACTOR) / MAX_COLOR);
+ f1 = COLOR_DARK_TS_FACTOR +
+ (backgroundBrightness *
+ (COLOR_LITE_TS_FACTOR - COLOR_DARK_TS_FACTOR) / MAX_COLOR);
+ }
+
+
+ r = rb - (f0 * rb / 100);
+ g = gb - (f0 * gb / 100);
+ b = bb - (f0 * bb / 100);
+ aResult[0] = NS_RGBA(r, g, b, a);
+
+ r = rb + (f1 * (MAX_COLOR - rb) / 100);
+ g = gb + (f1 * (MAX_COLOR - gb) / 100);
+ b = bb + (f1 * (MAX_COLOR - bb) / 100);
+ aResult[1] = NS_RGBA(r, g, b, a);
+}
+
+int NS_GetBrightness(uint8_t aRed, uint8_t aGreen, uint8_t aBlue)
+{
+
+ uint8_t intensity = (aRed + aGreen + aBlue) / 3;
+
+ uint8_t luminosity = NS_GetLuminosity(NS_RGB(aRed, aGreen, aBlue)) / 1000;
+
+ return ((intensity * INTENSITY_FACTOR) +
+ (luminosity * LUMINOSITY_FACTOR)) / 100;
+}
+
+int32_t NS_GetLuminosity(nscolor aColor)
+{
+ // When aColor is not opaque, the perceived luminosity will depend
+ // on what color(s) aColor is ultimately drawn on top of, which we
+ // do not know.
+ NS_ASSERTION(NS_GET_A(aColor) == 255,
+ "impossible to compute luminosity of a non-opaque color");
+
+ return (NS_GET_R(aColor) * RED_LUMINOSITY +
+ NS_GET_G(aColor) * GREEN_LUMINOSITY +
+ NS_GET_B(aColor) * BLUE_LUMINOSITY);
+}
+
+// Function to convert RGB color space into the HSV colorspace
+// Hue is the primary color defined from 0 to 359 degrees
+// Saturation is defined from 0 to 255. The higher the number.. the deeper
+// the color Value is the brightness of the color. 0 is black, 255 is white.
+void NS_RGB2HSV(nscolor aColor, uint16_t &aHue, uint16_t &aSat,
+ uint16_t &aValue, uint8_t &aAlpha)
+{
+ uint8_t r, g, b;
+ int16_t delta, min, max, r1, b1, g1;
+ float hue;
+
+ r = NS_GET_R(aColor);
+ g = NS_GET_G(aColor);
+ b = NS_GET_B(aColor);
+
+ if (r>g) {
+ max = r;
+ min = g;
+ } else {
+ max = g;
+ min = r;
+ }
+
+ if (b>max) {
+ max = b;
+ }
+ if (b<min) {
+ min = b;
+ }
+
+ // value or brightness will always be the max of all the colors(RGB)
+ aValue = max;
+ delta = max-min;
+ aSat = (max!=0)?((delta*255)/max):0;
+ r1 = r;
+ b1 = b;
+ g1 = g;
+
+ if (aSat==0) {
+ hue = 1000;
+ } else {
+ if(r==max){
+ hue=(float)(g1-b1)/(float)delta;
+ } else if (g1==max) {
+ hue= 2.0f+(float)(b1-r1)/(float)delta;
+ } else {
+ hue = 4.0f+(float)(r1-g1)/(float)delta;
+ }
+ }
+
+ if(hue<999) {
+ hue*=60;
+ if(hue<0){
+ hue+=360;
+ }
+ } else {
+ hue=0;
+ }
+
+ aHue = (uint16_t)hue;
+
+ aAlpha = NS_GET_A(aColor);
+}
+
+// Function to convert HSV color space into the RGB colorspace
+// Hue is the primary color defined from 0 to 359 degrees
+// Saturation is defined from 0 to 255. The higher the number.. the deeper
+// the color Value is the brightness of the color. 0 is black, 255 is white.
+void NS_HSV2RGB(nscolor &aColor, uint16_t aHue, uint16_t aSat, uint16_t aValue,
+ uint8_t aAlpha)
+{
+ uint16_t r = 0, g = 0, b = 0;
+ uint16_t i, p, q, t;
+ double h, f, percent;
+
+ if ( aSat == 0 ){
+ // achromatic color, no hue is defined
+ r = aValue;
+ g = aValue;
+ b = aValue;
+ } else {
+ // hue in in degrees around the color wheel defined from
+ // 0 to 360 degrees.
+ if (aHue >= 360) {
+ aHue = 0;
+ }
+
+ // we break the color wheel into 6 areas.. these
+ // areas define how the saturation and value define the color.
+ // reds behave differently than the blues
+ h = (double)aHue / 60.0;
+ i = (uint16_t) floor(h);
+ f = h-(double)i;
+ percent = ((double)aValue/255.0); // this needs to be a value from 0 to 1, so a percentage
+ // can be calculated of the saturation.
+ p = (uint16_t)(percent*(255-aSat));
+ q = (uint16_t)(percent*(255-(aSat*f)));
+ t = (uint16_t)(percent*(255-(aSat*(1.0-f))));
+
+ // i is guaranteed to never be larger than 5.
+ switch(i){
+ case 0: r = aValue; g = t; b = p;break;
+ case 1: r = q; g = aValue; b = p;break;
+ case 2: r = p; g = aValue; b = t;break;
+ case 3: r = p; g = q; b = aValue;break;
+ case 4: r = t; g = p; b = aValue;break;
+ case 5: r = aValue; g = p; b = q;break;
+ }
+ }
+ aColor = NS_RGBA(r, g, b, aAlpha);
+}
+
+#undef RED_LUMINOSITY
+#undef GREEN_LUMINOSITY
+#undef BLUE_LUMINOSITY
+#undef INTENSITY_FACTOR
+#undef LUMINOSITY_FACTOR
+
+#undef MAX_COLOR
+#undef COLOR_DARK_THRESHOLD
+#undef COLOR_LIGHT_THRESHOLD
+
+#undef COLOR_LITE_BS_FACTOR
+#undef COLOR_LITE_TS_FACTOR
+
+#undef COLOR_DARK_BS_FACTOR
+#undef COLOR_DARK_TS_FACTOR
+
+#undef LIGHT_GRAY
+#undef DARK_GRAY
+
+#undef MAX_BRIGHTNESS
+#undef MAX_DARKNESS
diff --git a/layout/base/nsCSSColorUtils.h b/layout/base/nsCSSColorUtils.h
new file mode 100644
index 000000000..f2106a7a2
--- /dev/null
+++ b/layout/base/nsCSSColorUtils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* functions that manipulate colors */
+
+#ifndef __nsCSSColorUtils_h
+#define __nsCSSColorUtils_h
+
+#include "nsColor.h"
+
+// "Sufficient contrast" is determined by
+// "Techniques For Accessibility Evalution And Repair Tools".
+// See http://www.w3.org/TR/AERT#color-contrast
+#define NS_SUFFICIENT_LUMINOSITY_DIFFERENCE 125000
+#define NS_LUMINOSITY_DIFFERENCE(a, b) \
+ int32_t(mozilla::Abs( \
+ NS_GetLuminosity(a | 0xff000000) - NS_GetLuminosity(b | 0xff000000)))
+
+// To determine colors based on the background brightness and border color
+void NS_GetSpecial3DColors(nscolor aResult[2],
+ nscolor aBackgroundColor,
+ nscolor aBorderColor);
+
+// Determins brightness for a specific color
+int NS_GetBrightness(uint8_t aRed, uint8_t aGreen, uint8_t aBlue);
+
+// Get Luminosity of a specific color. That is same as Y of YIQ color space.
+// The range of return value is 0 to 255000.
+int32_t NS_GetLuminosity(nscolor aColor);
+
+// function to convert from RGBA color space to HSVA color space
+void NS_RGB2HSV(nscolor aColor, uint16_t &aHue, uint16_t &aSat,
+ uint16_t &aValue, uint8_t &aAlpha);
+
+// function to convert from HSVA color space to RGBA color space
+void NS_HSV2RGB(nscolor &aColor, uint16_t aHue, uint16_t aSat, uint16_t aValue,
+ uint8_t aAlpha);
+
+#endif
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
new file mode 100644
index 000000000..a118c38f9
--- /dev/null
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -0,0 +1,12907 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * construction of a frame tree that is nearly isomorphic to the content
+ * tree and updating of that tree in response to dynamic changes
+ */
+
+#include "nsCSSFrameConstructor.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LinkedList.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIAtom.h"
+#include "nsIFrameInlines.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsIDocument.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsHTMLParts.h"
+#include "nsPresShell.h"
+#include "nsIPresShell.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsViewManager.h"
+#include "nsStyleConsts.h"
+#include "nsIDOMXULElement.h"
+#include "nsContainerFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsIListControlFrame.h"
+#include "nsIDOMCharacterData.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsIFormControl.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsTextFragment.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsBindingManager.h"
+#include "nsXBLBinding.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#ifdef XP_MACOSX
+#include "nsIDocShell.h"
+#endif
+#include "ChildIterator.h"
+#include "nsError.h"
+#include "nsLayoutUtils.h"
+#include "nsAutoPtr.h"
+#include "nsBoxFrame.h"
+#include "nsBoxLayout.h"
+#include "nsFlexContainerFrame.h"
+#include "nsGridContainerFrame.h"
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include "nsImageFrame.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsTArray.h"
+#include "nsGenericDOMDataNode.h"
+#include "mozilla/dom/Element.h"
+#include "nsAutoLayoutPhase.h"
+#include "nsStyleStructInlines.h"
+#include "nsPageContentFrame.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "StickyScrollContainer.h"
+#include "nsFieldSetFrame.h"
+#include "nsInlineFrame.h"
+#include "nsBlockFrame.h"
+#include "nsCanvasFrame.h"
+#include "nsFirstLetterFrame.h"
+#include "nsGfxScrollFrame.h"
+#include "nsPageFrame.h"
+#include "nsSimplePageSequenceFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsBackdropFrame.h"
+#include "nsTransitionManager.h"
+#include "DetailsFrame.h"
+
+#ifdef MOZ_XUL
+#include "nsIRootBox.h"
+#endif
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+#include "nsXBLService.h"
+
+#undef NOISY_FIRST_LETTER
+
+#include "nsMathMLParts.h"
+#include "mozilla/dom/SVGTests.h"
+#include "nsSVGUtils.h"
+
+#include "nsRefreshDriver.h"
+#include "nsRuleProcessorData.h"
+#include "nsTextNode.h"
+#include "ActiveLayerTracker.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// An alias for convenience.
+static const nsIFrame::ChildListID kPrincipalList = nsIFrame::kPrincipalList;
+
+nsIFrame*
+NS_NewHTMLCanvasFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewHTMLVideoFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsContainerFrame*
+NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame*
+NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGGenericContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame*
+NS_NewSVGForeignObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+extern nsIFrame*
+NS_NewSVGLinearGradientFrame(nsIPresShell *aPresShell, nsStyleContext* aContext);
+extern nsIFrame*
+NS_NewSVGRadialGradientFrame(nsIPresShell *aPresShell, nsStyleContext* aContext);
+extern nsIFrame*
+NS_NewSVGStopFrame(nsIPresShell *aPresShell, nsStyleContext* aContext);
+nsContainerFrame*
+NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame*
+NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+extern nsIFrame*
+NS_NewSVGImageFrame(nsIPresShell *aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGFilterFrame(nsIPresShell *aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGPatternFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGFEContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGFELeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGFEImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewSVGFEUnstyledLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+#include "mozilla/dom/NodeInfo.h"
+#include "prenv.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+
+#ifdef DEBUG
+// Set the environment variable GECKO_FRAMECTOR_DEBUG_FLAGS to one or
+// more of the following flags (comma separated) for handy debug
+// output.
+static bool gNoisyContentUpdates = false;
+static bool gReallyNoisyContentUpdates = false;
+static bool gNoisyInlineConstruction = false;
+
+struct FrameCtorDebugFlags {
+ const char* name;
+ bool* on;
+};
+
+static FrameCtorDebugFlags gFlags[] = {
+ { "content-updates", &gNoisyContentUpdates },
+ { "really-noisy-content-updates", &gReallyNoisyContentUpdates },
+ { "noisy-inline", &gNoisyInlineConstruction }
+};
+
+#define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
+#endif
+
+
+#ifdef MOZ_XUL
+#include "nsMenuFrame.h"
+#include "nsPopupSetFrame.h"
+#include "nsTreeColFrame.h"
+#include "nsIBoxObject.h"
+#include "nsPIListBoxObject.h"
+#include "nsListBoxBodyFrame.h"
+#include "nsListItemFrame.h"
+#include "nsXULLabelFrame.h"
+
+//------------------------------------------------------------------
+
+nsIFrame*
+NS_NewAutoRepeatBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsContainerFrame*
+NS_NewRootBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsContainerFrame*
+NS_NewDocElementBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewDeckFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewLeafBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewStackFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewProgressMeterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewRangeFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewImageBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewGroupBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewButtonBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewSplitterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewMenuPopupFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewMenuFrame (nsIPresShell* aPresShell, nsStyleContext* aContext, uint32_t aFlags);
+
+nsIFrame*
+NS_NewMenuBarFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewTreeBodyFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+// grid
+nsresult
+NS_NewGridLayout2 ( nsIPresShell* aPresShell, nsBoxLayout** aNewLayout );
+nsIFrame*
+NS_NewGridRowLeafFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewGridRowGroupFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+// end grid
+
+nsIFrame*
+NS_NewTitleBarFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewResizerFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+
+#endif
+
+nsHTMLScrollFrame*
+NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot);
+
+nsXULScrollFrame*
+NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext,
+ bool aIsRoot, bool aClipAllDescendants);
+
+nsIFrame*
+NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewScrollbarFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
+NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+
+#ifdef NOISY_FINDFRAME
+static int32_t FFWC_totalCount=0;
+static int32_t FFWC_doLoop=0;
+static int32_t FFWC_doSibling=0;
+static int32_t FFWC_recursions=0;
+static int32_t FFWC_nextInFlows=0;
+#endif
+
+// Returns true if aFrame is an anonymous flex/grid item.
+static inline bool
+IsAnonymousFlexOrGridItem(const nsIFrame* aFrame)
+{
+ const nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo();
+ return pseudoType == nsCSSAnonBoxes::anonymousFlexItem ||
+ pseudoType == nsCSSAnonBoxes::anonymousGridItem;
+}
+
+// Returns true if aFrame is a flex/grid container.
+static inline bool
+IsFlexOrGridContainer(const nsIFrame* aFrame)
+{
+ const nsIAtom* t = aFrame->GetType();
+ return t == nsGkAtoms::flexContainerFrame ||
+ t == nsGkAtoms::gridContainerFrame;
+}
+
+// Returns true IFF the given nsIFrame is a nsFlexContainerFrame and
+// represents a -webkit-{inline-}box container. The frame's GetType() result is
+// passed as a separate argument, since in most cases, the caller will have
+// looked it up already, and we'd rather not make redundant calls to the
+// virtual GetType() method.
+static inline bool
+IsFlexContainerForLegacyBox(const nsIFrame* aFrame,
+ const nsIAtom* aFrameType)
+{
+ MOZ_ASSERT(aFrame->GetType() == aFrameType, "wrong aFrameType was passed");
+ return aFrameType == nsGkAtoms::flexContainerFrame &&
+ aFrame->HasAnyStateBits(NS_STATE_FLEX_IS_LEGACY_WEBKIT_BOX);
+}
+
+#if DEBUG
+static void
+AssertAnonymousFlexOrGridItemParent(const nsIFrame* aChild,
+ const nsIFrame* aParent)
+{
+ MOZ_ASSERT(IsAnonymousFlexOrGridItem(aChild),
+ "expected an anonymous flex or grid item child frame");
+ MOZ_ASSERT(aParent, "expected a parent frame");
+ const nsIAtom* pseudoType = aChild->StyleContext()->GetPseudo();
+ if (pseudoType == nsCSSAnonBoxes::anonymousFlexItem) {
+ MOZ_ASSERT(aParent->GetType() == nsGkAtoms::flexContainerFrame,
+ "anonymous flex items should only exist as children "
+ "of flex container frames");
+ } else {
+ MOZ_ASSERT(aParent->GetType() == nsGkAtoms::gridContainerFrame,
+ "anonymous grid items should only exist as children "
+ "of grid container frames");
+ }
+}
+#else
+#define AssertAnonymousFlexOrGridItemParent(x, y) do { /* nothing */ } while(0)
+#endif
+
+static inline nsContainerFrame*
+GetFieldSetBlockFrame(nsIFrame* aFieldsetFrame)
+{
+ // Depends on the fieldset child frame order - see ConstructFieldSetFrame() below.
+ nsIFrame* firstChild = aFieldsetFrame->PrincipalChildList().FirstChild();
+ nsIFrame* inner = firstChild && firstChild->GetNextSibling() ? firstChild->GetNextSibling() : firstChild;
+ return inner ? inner->GetContentInsertionFrame() : nullptr;
+}
+
+#define FCDATA_DECL(_flags, _func) \
+ { _flags, { (FrameCreationFunc)_func }, nullptr, nullptr }
+#define FCDATA_WITH_WRAPPING_BLOCK(_flags, _func, _anon_box) \
+ { _flags | FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS, \
+ { (FrameCreationFunc)_func }, nullptr, &_anon_box }
+
+#define UNREACHABLE_FCDATA() \
+ { 0, { (FrameCreationFunc)nullptr }, nullptr, nullptr }
+//----------------------------------------------------------------------
+
+/**
+ * True if aFrame is an actual inline frame in the sense of non-replaced
+ * display:inline CSS boxes. In other words, it can be affected by {ib}
+ * splitting and can contain first-letter frames. Basically, this is either an
+ * inline frame (positioned or otherwise) or an line frame (this last because
+ * it can contain first-letter and because inserting blocks in the middle of it
+ * needs to terminate it).
+ */
+static bool
+IsInlineFrame(const nsIFrame* aFrame)
+{
+ return aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
+}
+
+/**
+ * True if aFrame is an instance of an SVG frame class or is an inline/block
+ * frame being used for SVG text.
+ */
+static bool
+IsFrameForSVG(const nsIFrame* aFrame)
+{
+ return aFrame->IsFrameOfType(nsIFrame::eSVG) ||
+ aFrame->IsSVGText();
+}
+
+/**
+ * Returns true iff aFrame explicitly prevents its descendants from floating
+ * (at least, down to the level of descendants which themselves are
+ * float-containing blocks -- those will manage the floating status of any
+ * lower-level descendents inside them, of course).
+ */
+static bool
+ShouldSuppressFloatingOfDescendants(nsIFrame* aFrame)
+{
+ return aFrame->IsFrameOfType(nsIFrame::eMathML) ||
+ aFrame->IsXULBoxFrame() ||
+ ::IsFlexOrGridContainer(aFrame);
+}
+
+/**
+ * If any children require a block parent, return the first such child.
+ * Otherwise return null.
+ */
+static nsIContent*
+AnyKidsNeedBlockParent(nsIFrame *aFrameList)
+{
+ for (nsIFrame *k = aFrameList; k; k = k->GetNextSibling()) {
+ // Line participants, such as text and inline frames, can't be
+ // directly inside a XUL box; they must be wrapped in an
+ // intermediate block.
+ if (k->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ return k->GetContent();
+ }
+ }
+ return nullptr;
+}
+
+// Reparent a frame into a wrapper frame that is a child of its old parent.
+static void
+ReparentFrame(RestyleManagerHandle aRestyleManager,
+ nsContainerFrame* aNewParentFrame,
+ nsIFrame* aFrame)
+{
+ aFrame->SetParent(aNewParentFrame);
+ aRestyleManager->ReparentStyleContext(aFrame);
+}
+
+static void
+ReparentFrames(nsCSSFrameConstructor* aFrameConstructor,
+ nsContainerFrame* aNewParentFrame,
+ const nsFrameList& aFrameList)
+{
+ RestyleManagerHandle restyleManager = aFrameConstructor->RestyleManager();
+ for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+ ReparentFrame(restyleManager, aNewParentFrame, e.get());
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// When inline frames get weird and have block frames in them, we
+// annotate them to help us respond to incremental content changes
+// more easily.
+
+static inline bool
+IsFramePartOfIBSplit(nsIFrame* aFrame)
+{
+ return (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) != 0;
+}
+
+static nsContainerFrame* GetIBSplitSibling(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
+
+ // We only store the "ib-split sibling" annotation with the first
+ // frame in the continuation chain. Walk back to find that frame now.
+ return static_cast<nsContainerFrame*>
+ (aFrame->FirstContinuation()->
+ Properties().Get(nsIFrame::IBSplitSibling()));
+}
+
+static nsContainerFrame* GetIBSplitPrevSibling(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
+
+ // We only store the ib-split sibling annotation with the first
+ // frame in the continuation chain. Walk back to find that frame now.
+ return static_cast<nsContainerFrame*>
+ (aFrame->FirstContinuation()->
+ Properties().Get(nsIFrame::IBSplitPrevSibling()));
+}
+
+static nsContainerFrame*
+GetLastIBSplitSibling(nsIFrame* aFrame, bool aReturnEmptyTrailingInline)
+{
+ for (nsIFrame *frame = aFrame, *next; ; frame = next) {
+ next = GetIBSplitSibling(frame);
+ if (!next ||
+ (!aReturnEmptyTrailingInline && !next->PrincipalChildList().FirstChild() &&
+ !GetIBSplitSibling(next))) {
+ NS_ASSERTION(!next || !frame->IsInlineOutside(),
+ "Should have a block here!");
+ return static_cast<nsContainerFrame*>(frame);
+ }
+ }
+ NS_NOTREACHED("unreachable code");
+ return nullptr;
+}
+
+static void
+SetFrameIsIBSplit(nsContainerFrame* aFrame, nsIFrame* aIBSplitSibling)
+{
+ NS_PRECONDITION(aFrame, "bad args!");
+
+ // We should be the only continuation
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "assigning ib-split sibling to other than first continuation!");
+ NS_ASSERTION(!aFrame->GetNextContinuation() ||
+ IsFramePartOfIBSplit(aFrame->GetNextContinuation()),
+ "should have no non-ib-split continuations here");
+
+ // Mark the frame as ib-split.
+ aFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT);
+
+ if (aIBSplitSibling) {
+ NS_ASSERTION(!aIBSplitSibling->GetPrevContinuation(),
+ "assigning something other than the first continuation as the "
+ "ib-split sibling");
+
+ // Store the ib-split sibling (if we were given one) with the
+ // first frame in the flow.
+ FramePropertyTable* props = aFrame->PresContext()->PropertyTable();
+ props->Set(aFrame, nsIFrame::IBSplitSibling(), aIBSplitSibling);
+ props->Set(aIBSplitSibling, nsIFrame::IBSplitPrevSibling(), aFrame);
+ }
+}
+
+static nsIFrame*
+GetIBContainingBlockFor(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(IsFramePartOfIBSplit(aFrame),
+ "GetIBContainingBlockFor() should only be called on known IB frames");
+
+ // Get the first "normal" ancestor of the target frame.
+ nsIFrame* parentFrame;
+ do {
+ parentFrame = aFrame->GetParent();
+
+ if (! parentFrame) {
+ NS_ERROR("no unsplit block frame in IB hierarchy");
+ return aFrame;
+ }
+
+ // Note that we ignore non-ib-split frames which have a pseudo on their
+ // style context -- they're not the frames we're looking for! In
+ // particular, they may be hiding a real parent that _is_ in an ib-split.
+ if (!IsFramePartOfIBSplit(parentFrame) &&
+ !parentFrame->StyleContext()->GetPseudo())
+ break;
+
+ aFrame = parentFrame;
+ } while (1);
+
+ // post-conditions
+ NS_ASSERTION(parentFrame, "no normal ancestor found for ib-split frame "
+ "in GetIBContainingBlockFor");
+ NS_ASSERTION(parentFrame != aFrame, "parentFrame is actually the child frame - bogus reslt");
+
+ return parentFrame;
+}
+
+//----------------------------------------------------------------------
+
+// Block/inline frame construction logic. We maintain a few invariants here:
+//
+// 1. Block frames contain block and inline frames.
+//
+// 2. Inline frames only contain inline frames. If an inline parent has a block
+// child then the block child is migrated upward until it lands in a block
+// parent (the inline frames containing block is where it will end up).
+
+// After this function returns, aLink is pointing to the first link at or
+// after its starting position for which the next frame is a block. If there
+// is no such link, it points to the end of the list.
+static void
+FindFirstBlock(nsFrameList::FrameLinkEnumerator& aLink)
+{
+ for ( ; !aLink.AtEnd(); aLink.Next()) {
+ if (!aLink.NextFrame()->IsInlineOutside()) {
+ return;
+ }
+ }
+}
+
+// This function returns a frame link enumerator pointing to the first link in
+// the list for which the next frame is not block. If there is no such link,
+// it points to the end of the list.
+static nsFrameList::FrameLinkEnumerator
+FindFirstNonBlock(const nsFrameList& aList)
+{
+ nsFrameList::FrameLinkEnumerator link(aList);
+ for (; !link.AtEnd(); link.Next()) {
+ if (link.NextFrame()->IsInlineOutside()) {
+ break;
+ }
+ }
+ return link;
+}
+
+inline void
+SetInitialSingleChild(nsContainerFrame* aParent, nsIFrame* aFrame)
+{
+ NS_PRECONDITION(!aFrame->GetNextSibling(), "Should be using a frame list");
+ nsFrameList temp(aFrame, aFrame);
+ aParent->SetInitialChildList(kPrincipalList, temp);
+}
+
+// -----------------------------------------------------------
+
+// Structure used when constructing formatting object trees.
+struct nsFrameItems : public nsFrameList
+{
+ // Appends the frame to the end of the list
+ void AddChild(nsIFrame* aChild);
+};
+
+void
+nsFrameItems::AddChild(nsIFrame* aChild)
+{
+ NS_PRECONDITION(aChild, "nsFrameItems::AddChild");
+
+ // It'd be really nice if we could just AppendFrames(kPrincipalList, aChild) here,
+ // but some of our callers put frames that have different
+ // parents (caption, I'm looking at you) on the same framelist, and
+ // nsFrameList asserts if you try to do that.
+ if (IsEmpty()) {
+ SetFrames(aChild);
+ }
+ else {
+ NS_ASSERTION(aChild != mLastChild,
+ "Same frame being added to frame list twice?");
+ mLastChild->SetNextSibling(aChild);
+ mLastChild = nsLayoutUtils::GetLastSibling(aChild);
+ }
+}
+
+// -----------------------------------------------------------
+
+// Structure used when constructing formatting object trees. Contains
+// state information needed for absolutely positioned elements
+struct nsAbsoluteItems : nsFrameItems {
+ // containing block for absolutely positioned elements
+ nsContainerFrame* containingBlock;
+
+ explicit nsAbsoluteItems(nsContainerFrame* aContainingBlock);
+#ifdef DEBUG
+ // XXXbz Does this need a debug-only assignment operator that nulls out the
+ // childList in the nsAbsoluteItems we're copying? Introducing a difference
+ // between debug and non-debug behavior seems bad, so I guess not...
+ ~nsAbsoluteItems() {
+ NS_ASSERTION(!FirstChild(),
+ "Dangling child list. Someone forgot to insert it?");
+ }
+#endif
+
+ // Appends the frame to the end of the list
+ void AddChild(nsIFrame* aChild);
+};
+
+nsAbsoluteItems::nsAbsoluteItems(nsContainerFrame* aContainingBlock)
+ : containingBlock(aContainingBlock)
+{
+}
+
+// Additional behavior is that it sets the frame's NS_FRAME_OUT_OF_FLOW flag
+void
+nsAbsoluteItems::AddChild(nsIFrame* aChild)
+{
+ NS_ASSERTION(aChild->PresContext()->FrameManager()->
+ GetPlaceholderFrameFor(aChild),
+ "Child without placeholder being added to nsAbsoluteItems?");
+ aChild->AddStateBits(NS_FRAME_OUT_OF_FLOW);
+ nsFrameItems::AddChild(aChild);
+}
+
+// -----------------------------------------------------------
+
+// Structure for saving the existing state when pushing/poping containing
+// blocks. The destructor restores the state to its previous state
+class MOZ_STACK_CLASS nsFrameConstructorSaveState {
+public:
+ typedef nsIFrame::ChildListID ChildListID;
+ nsFrameConstructorSaveState();
+ ~nsFrameConstructorSaveState();
+
+private:
+ nsAbsoluteItems* mItems; // pointer to struct whose data we save/restore
+ nsAbsoluteItems mSavedItems; // copy of original data
+
+ // The name of the child list in which our frames would belong
+ ChildListID mChildListID;
+ nsFrameConstructorState* mState;
+
+ // State used only when we're saving the abs-pos state for a transformed
+ // element.
+ nsAbsoluteItems mSavedFixedItems;
+
+ bool mSavedFixedPosIsAbsPos;
+
+ friend class nsFrameConstructorState;
+};
+
+// Structure used to keep track of a list of bindings we need to call
+// AddToAttachedQueue on. These should be in post-order depth-first
+// flattened tree traversal order.
+struct PendingBinding : public LinkedListElement<PendingBinding>
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+ PendingBinding() {
+ MOZ_COUNT_CTOR(PendingBinding);
+ }
+ ~PendingBinding() {
+ MOZ_COUNT_DTOR(PendingBinding);
+ }
+#endif
+
+ RefPtr<nsXBLBinding> mBinding;
+};
+
+// Structure used for maintaining state information during the
+// frame construction process
+class MOZ_STACK_CLASS nsFrameConstructorState {
+public:
+ typedef nsIFrame::ChildListID ChildListID;
+
+ nsPresContext *mPresContext;
+ nsIPresShell *mPresShell;
+ nsFrameManager *mFrameManager;
+
+#ifdef MOZ_XUL
+ // Frames destined for the kPopupList.
+ nsAbsoluteItems mPopupItems;
+#endif
+
+ // Containing block information for out-of-flow frames.
+ nsAbsoluteItems mFixedItems;
+ nsAbsoluteItems mAbsoluteItems;
+ nsAbsoluteItems mFloatedItems;
+ // The containing block of a frame in the top layer is defined by the
+ // spec: fixed-positioned frames are children of the viewport frame,
+ // and absolutely-positioned frames are children of the initial
+ // containing block. They would not be caught by any other containing
+ // block, e.g. frames with transform or filter.
+ nsAbsoluteItems mTopLayerFixedItems;
+ nsAbsoluteItems mTopLayerAbsoluteItems;
+
+ nsCOMPtr<nsILayoutHistoryState> mFrameState;
+ // These bits will be added to the state bits of any frame we construct
+ // using this state.
+ nsFrameState mAdditionalStateBits;
+
+ // When working with the transform and filter properties, we want to hook
+ // the abs-pos and fixed-pos lists together, since such
+ // elements are fixed-pos containing blocks. This flag determines
+ // whether or not we want to wire the fixed-pos and abs-pos lists
+ // together.
+ bool mFixedPosIsAbsPos;
+
+ // A boolean to indicate whether we have a "pending" popupgroup. That is, we
+ // have already created the FrameConstructionItem for the root popupgroup but
+ // we have not yet created the relevant frame.
+ bool mHavePendingPopupgroup;
+
+ // If false (which is the default) then call SetPrimaryFrame() as needed
+ // during frame construction. If true, don't make any SetPrimaryFrame()
+ // calls, except for generated content which doesn't have a primary frame
+ // yet. The mCreatingExtraFrames == true mode is meant to be used for
+ // construction of random "extra" frames for elements via normal frame
+ // construction APIs (e.g. replication of things across pages in paginated
+ // mode).
+ bool mCreatingExtraFrames;
+
+ nsCOMArray<nsIContent> mGeneratedTextNodesWithInitializer;
+
+ TreeMatchContext mTreeMatchContext;
+
+ // Constructor
+ // Use the passed-in history state.
+ nsFrameConstructorState(
+ nsIPresShell* aPresShell,
+ nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock,
+ already_AddRefed<nsILayoutHistoryState> aHistoryState);
+ // Get the history state from the pres context's pres shell.
+ nsFrameConstructorState(nsIPresShell* aPresShell,
+ nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock);
+
+ ~nsFrameConstructorState();
+
+ // Function to push the existing absolute containing block state and
+ // create a new scope. Code that uses this function should get matching
+ // logic in GetAbsoluteContainingBlock.
+ // Also makes aNewAbsoluteContainingBlock the containing block for
+ // fixed-pos elements if necessary.
+ // aPositionedFrame is the frame whose style actually makes
+ // aNewAbsoluteContainingBlock a containing block. E.g. for a scrollable element
+ // aPositionedFrame is the element's primary frame and
+ // aNewAbsoluteContainingBlock is the scrolled frame.
+ void PushAbsoluteContainingBlock(nsContainerFrame* aNewAbsoluteContainingBlock,
+ nsIFrame* aPositionedFrame,
+ nsFrameConstructorSaveState& aSaveState);
+
+ // Function to push the existing float containing block state and
+ // create a new scope. Code that uses this function should get matching
+ // logic in GetFloatContainingBlock.
+ // Pushing a null float containing block forbids any frames from being
+ // floated until a new float containing block is pushed.
+ // XXX we should get rid of null float containing blocks and teach the
+ // various frame classes to deal with floats instead.
+ void PushFloatContainingBlock(nsContainerFrame* aNewFloatContainingBlock,
+ nsFrameConstructorSaveState& aSaveState);
+
+ // Function to return the proper geometric parent for a frame with display
+ // struct given by aStyleDisplay and parent's frame given by
+ // aContentParentFrame.
+ nsContainerFrame* GetGeometricParent(const nsStyleDisplay* aStyleDisplay,
+ nsContainerFrame* aContentParentFrame) const;
+
+ /**
+ * Function to add a new frame to the right frame list. This MUST be called
+ * on frames before their children have been processed if the frames might
+ * conceivably be out-of-flow; otherwise cleanup in error cases won't work
+ * right. Also, this MUST be called on frames after they have been
+ * initialized.
+ * @param aNewFrame the frame to add
+ * @param aFrameItems the list to add in-flow frames to
+ * @param aContent the content pointer for aNewFrame
+ * @param aStyleContext the style context resolved for aContent
+ * @param aParentFrame the parent frame for the content if it were in-flow
+ * @param aCanBePositioned pass false if the frame isn't allowed to be
+ * positioned
+ * @param aCanBeFloated pass false if the frame isn't allowed to be
+ * floated
+ * @param aIsOutOfFlowPopup pass true if the frame is an out-of-flow popup
+ * (XUL-only)
+ */
+ void AddChild(nsIFrame* aNewFrame,
+ nsFrameItems& aFrameItems,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame* aParentFrame,
+ bool aCanBePositioned = true,
+ bool aCanBeFloated = true,
+ bool aIsOutOfFlowPopup = false,
+ bool aInsertAfter = false,
+ nsIFrame* aInsertAfterFrame = nullptr);
+
+ /**
+ * Function to return the fixed-pos element list. Normally this will just hand back the
+ * fixed-pos element list, but in case we're dealing with a transformed element that's
+ * acting as an abs-pos and fixed-pos container, we'll hand back the abs-pos list. Callers should
+ * use this function if they want to get the list acting as the fixed-pos item parent.
+ */
+ nsAbsoluteItems& GetFixedItems()
+ {
+ return mFixedPosIsAbsPos ? mAbsoluteItems : mFixedItems;
+ }
+ const nsAbsoluteItems& GetFixedItems() const
+ {
+ return mFixedPosIsAbsPos ? mAbsoluteItems : mFixedItems;
+ }
+
+
+ /**
+ * class to automatically push and pop a pending binding in the frame
+ * constructor state. See nsCSSFrameConstructor::FrameConstructionItem
+ * mPendingBinding documentation.
+ */
+ class PendingBindingAutoPusher;
+ friend class PendingBindingAutoPusher;
+ class MOZ_STACK_CLASS PendingBindingAutoPusher {
+ public:
+ PendingBindingAutoPusher(nsFrameConstructorState& aState,
+ PendingBinding* aPendingBinding) :
+ mState(aState),
+ mPendingBinding(aState.mCurrentPendingBindingInsertionPoint)
+ {
+ if (aPendingBinding) {
+ aState.mCurrentPendingBindingInsertionPoint = aPendingBinding;
+ }
+ }
+
+ ~PendingBindingAutoPusher()
+ {
+ mState.mCurrentPendingBindingInsertionPoint = mPendingBinding;
+ }
+
+ private:
+ nsFrameConstructorState& mState;
+ PendingBinding* mPendingBinding;
+ };
+
+ /**
+ * Add a new pending binding to the list
+ */
+ void AddPendingBinding(PendingBinding* aPendingBinding) {
+ if (mCurrentPendingBindingInsertionPoint) {
+ mCurrentPendingBindingInsertionPoint->setPrevious(aPendingBinding);
+ } else {
+ mPendingBindings.insertBack(aPendingBinding);
+ }
+ }
+
+protected:
+ friend class nsFrameConstructorSaveState;
+
+ /**
+ * ProcessFrameInsertions takes the frames in aFrameItems and adds them as
+ * kids to the aChildListID child list of |aFrameItems.containingBlock|.
+ */
+ void ProcessFrameInsertions(nsAbsoluteItems& aFrameItems,
+ ChildListID aChildListID);
+
+ /**
+ * GetOutOfFlowFrameItems selects the out-of-flow frame list the new
+ * frame should be added to. If the frame shouldn't be added to any
+ * out-of-flow list, it returns nullptr. The corresponding type of
+ * placeholder is also returned via the aPlaceholderType parameter
+ * if this method doesn't return nullptr. The caller should check
+ * whether the returned list really has a containing block.
+ */
+ nsAbsoluteItems* GetOutOfFlowFrameItems(nsIFrame* aNewFrame,
+ bool aCanBePositioned,
+ bool aCanBeFloated,
+ bool aIsOutOfFlowPopup,
+ nsFrameState* aPlaceholderType);
+
+ void ConstructBackdropFrameFor(nsIContent* aContent, nsIFrame* aFrame);
+
+ // Our list of all pending bindings. When we're done, we need to call
+ // AddToAttachedQueue on all of them, in order.
+ LinkedList<PendingBinding> mPendingBindings;
+
+ PendingBinding* mCurrentPendingBindingInsertionPoint;
+};
+
+nsFrameConstructorState::nsFrameConstructorState(
+ nsIPresShell* aPresShell,
+ nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock,
+ already_AddRefed<nsILayoutHistoryState> aHistoryState)
+ : mPresContext(aPresShell->GetPresContext()),
+ mPresShell(aPresShell),
+ mFrameManager(aPresShell->FrameManager()),
+#ifdef MOZ_XUL
+ mPopupItems(nullptr),
+#endif
+ mFixedItems(aFixedContainingBlock),
+ mAbsoluteItems(aAbsoluteContainingBlock),
+ mFloatedItems(aFloatContainingBlock),
+ mTopLayerFixedItems(
+ static_cast<nsContainerFrame*>(mFrameManager->GetRootFrame())),
+ mTopLayerAbsoluteItems(
+ aPresShell->FrameConstructor()->GetDocElementContainingBlock()),
+ // See PushAbsoluteContaningBlock below
+ mFrameState(aHistoryState),
+ mAdditionalStateBits(nsFrameState(0)),
+ // If the fixed-pos containing block is equal to the abs-pos containing
+ // block, use the abs-pos containing block's abs-pos list for fixed-pos
+ // frames.
+ mFixedPosIsAbsPos(aFixedContainingBlock == aAbsoluteContainingBlock),
+ mHavePendingPopupgroup(false),
+ mCreatingExtraFrames(false),
+ mTreeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited,
+ aPresShell->GetDocument()),
+ mCurrentPendingBindingInsertionPoint(nullptr)
+{
+#ifdef MOZ_XUL
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(aPresShell);
+ if (rootBox) {
+ mPopupItems.containingBlock = rootBox->GetPopupSetFrame();
+ }
+#endif
+ MOZ_COUNT_CTOR(nsFrameConstructorState);
+}
+
+nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell,
+ nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock)
+ : nsFrameConstructorState(aPresShell, aFixedContainingBlock,
+ aAbsoluteContainingBlock,
+ aFloatContainingBlock,
+ aPresShell->GetDocument()->GetLayoutHistoryState())
+{
+}
+
+nsFrameConstructorState::~nsFrameConstructorState()
+{
+ MOZ_COUNT_DTOR(nsFrameConstructorState);
+ ProcessFrameInsertions(mTopLayerFixedItems, nsIFrame::kFixedList);
+ ProcessFrameInsertions(mTopLayerAbsoluteItems, nsIFrame::kAbsoluteList);
+ ProcessFrameInsertions(mFloatedItems, nsIFrame::kFloatList);
+ ProcessFrameInsertions(mAbsoluteItems, nsIFrame::kAbsoluteList);
+ ProcessFrameInsertions(mFixedItems, nsIFrame::kFixedList);
+#ifdef MOZ_XUL
+ ProcessFrameInsertions(mPopupItems, nsIFrame::kPopupList);
+#endif
+ for (int32_t i = mGeneratedTextNodesWithInitializer.Count() - 1; i >= 0; --i) {
+ mGeneratedTextNodesWithInitializer[i]->
+ DeleteProperty(nsGkAtoms::genConInitializerProperty);
+ }
+ if (!mPendingBindings.isEmpty()) {
+ nsBindingManager* bindingManager = mPresShell->GetDocument()->BindingManager();
+ do {
+ nsAutoPtr<PendingBinding> pendingBinding;
+ pendingBinding = mPendingBindings.popFirst();
+ bindingManager->AddToAttachedQueue(pendingBinding->mBinding);
+ } while (!mPendingBindings.isEmpty());
+ mCurrentPendingBindingInsertionPoint = nullptr;
+ }
+}
+
+static nsContainerFrame*
+AdjustAbsoluteContainingBlock(nsContainerFrame* aContainingBlockIn)
+{
+ if (!aContainingBlockIn) {
+ return nullptr;
+ }
+
+ // Always use the container's first continuation. (Inline frames can have
+ // non-fluid bidi continuations...)
+ return static_cast<nsContainerFrame*>(aContainingBlockIn->FirstContinuation());
+}
+
+void
+nsFrameConstructorState::PushAbsoluteContainingBlock(nsContainerFrame* aNewAbsoluteContainingBlock,
+ nsIFrame* aPositionedFrame,
+ nsFrameConstructorSaveState& aSaveState)
+{
+ aSaveState.mItems = &mAbsoluteItems;
+ aSaveState.mSavedItems = mAbsoluteItems;
+ aSaveState.mChildListID = nsIFrame::kAbsoluteList;
+ aSaveState.mState = this;
+ aSaveState.mSavedFixedPosIsAbsPos = mFixedPosIsAbsPos;
+
+ if (mFixedPosIsAbsPos) {
+ // Since we're going to replace mAbsoluteItems, we need to save it into
+ // mFixedItems now (and save the current value of mFixedItems).
+ aSaveState.mSavedFixedItems = mFixedItems;
+ mFixedItems = mAbsoluteItems;
+ }
+
+ mAbsoluteItems =
+ nsAbsoluteItems(AdjustAbsoluteContainingBlock(aNewAbsoluteContainingBlock));
+
+ /* See if we're wiring the fixed-pos and abs-pos lists together. This happens iff
+ * we're a transformed element.
+ */
+ mFixedPosIsAbsPos = aPositionedFrame &&
+ aPositionedFrame->IsFixedPosContainingBlock();
+
+ if (aNewAbsoluteContainingBlock) {
+ aNewAbsoluteContainingBlock->MarkAsAbsoluteContainingBlock();
+ }
+}
+
+void
+nsFrameConstructorState::PushFloatContainingBlock(nsContainerFrame* aNewFloatContainingBlock,
+ nsFrameConstructorSaveState& aSaveState)
+{
+ NS_PRECONDITION(!aNewFloatContainingBlock ||
+ aNewFloatContainingBlock->IsFloatContainingBlock(),
+ "Please push a real float containing block!");
+ NS_ASSERTION(!aNewFloatContainingBlock ||
+ !ShouldSuppressFloatingOfDescendants(aNewFloatContainingBlock),
+ "We should not push a frame that is supposed to _suppress_ "
+ "floats as a float containing block!");
+ aSaveState.mItems = &mFloatedItems;
+ aSaveState.mSavedItems = mFloatedItems;
+ aSaveState.mChildListID = nsIFrame::kFloatList;
+ aSaveState.mState = this;
+ mFloatedItems = nsAbsoluteItems(aNewFloatContainingBlock);
+}
+
+nsContainerFrame*
+nsFrameConstructorState::GetGeometricParent(const nsStyleDisplay* aStyleDisplay,
+ nsContainerFrame* aContentParentFrame) const
+{
+ NS_PRECONDITION(aStyleDisplay, "Must have display struct!");
+
+ // If there is no container for a fixed, absolute, or floating root
+ // frame, we will ignore the positioning. This hack is originally
+ // brought to you by the letter T: tables, since other roots don't
+ // even call into this code. See bug 178855.
+ //
+ // XXX Disabling positioning in this case is a hack. If one was so inclined,
+ // one could support this either by (1) inserting a dummy block between the
+ // table and the canvas or (2) teaching the canvas how to reflow positioned
+ // elements. (1) has the usual problems when multiple frames share the same
+ // content (notice all the special cases in this file dealing with inner
+ // tables and table wrappers which share the same content). (2) requires some
+ // work and possible factoring.
+ //
+ // XXXbz couldn't we just force position to "static" on roots and
+ // float to "none"? That's OK per CSS 2.1, as far as I can tell.
+
+ if (aContentParentFrame && aContentParentFrame->IsSVGText()) {
+ return aContentParentFrame;
+ }
+
+ if (aStyleDisplay->IsFloatingStyle() && mFloatedItems.containingBlock) {
+ NS_ASSERTION(!aStyleDisplay->IsAbsolutelyPositionedStyle(),
+ "Absolutely positioned _and_ floating?");
+ return mFloatedItems.containingBlock;
+ }
+
+ if (aStyleDisplay->mTopLayer != NS_STYLE_TOP_LAYER_NONE) {
+ MOZ_ASSERT(aStyleDisplay->mTopLayer == NS_STYLE_TOP_LAYER_TOP,
+ "-moz-top-layer should be either none or top");
+ MOZ_ASSERT(aStyleDisplay->IsAbsolutelyPositionedStyle(),
+ "Top layer items should always be absolutely positioned");
+ if (aStyleDisplay->mPosition == NS_STYLE_POSITION_FIXED) {
+ MOZ_ASSERT(mTopLayerFixedItems.containingBlock, "No root frame?");
+ return mTopLayerFixedItems.containingBlock;
+ }
+ MOZ_ASSERT(aStyleDisplay->mPosition == NS_STYLE_POSITION_ABSOLUTE);
+ MOZ_ASSERT(mTopLayerAbsoluteItems.containingBlock);
+ return mTopLayerAbsoluteItems.containingBlock;
+ }
+
+ if (aStyleDisplay->mPosition == NS_STYLE_POSITION_ABSOLUTE &&
+ mAbsoluteItems.containingBlock) {
+ return mAbsoluteItems.containingBlock;
+ }
+
+ if (aStyleDisplay->mPosition == NS_STYLE_POSITION_FIXED &&
+ GetFixedItems().containingBlock) {
+ return GetFixedItems().containingBlock;
+ }
+
+ return aContentParentFrame;
+}
+
+nsAbsoluteItems*
+nsFrameConstructorState::GetOutOfFlowFrameItems(nsIFrame* aNewFrame,
+ bool aCanBePositioned,
+ bool aCanBeFloated,
+ bool aIsOutOfFlowPopup,
+ nsFrameState* aPlaceholderType)
+{
+#ifdef MOZ_XUL
+ if (MOZ_UNLIKELY(aIsOutOfFlowPopup)) {
+ MOZ_ASSERT(mPopupItems.containingBlock, "Must have a popup set frame!");
+ *aPlaceholderType = PLACEHOLDER_FOR_POPUP;
+ return &mPopupItems;
+ }
+#endif // MOZ_XUL
+ if (aCanBeFloated && aNewFrame->IsFloating()) {
+ *aPlaceholderType = PLACEHOLDER_FOR_FLOAT;
+ return &mFloatedItems;
+ }
+
+ if (aCanBePositioned) {
+ const nsStyleDisplay* disp = aNewFrame->StyleDisplay();
+ if (disp->mTopLayer != NS_STYLE_TOP_LAYER_NONE) {
+ *aPlaceholderType = PLACEHOLDER_FOR_TOPLAYER;
+ if (disp->mPosition == NS_STYLE_POSITION_FIXED) {
+ return &mTopLayerFixedItems;
+ }
+ return &mTopLayerAbsoluteItems;
+ }
+ if (disp->mPosition == NS_STYLE_POSITION_ABSOLUTE) {
+ *aPlaceholderType = PLACEHOLDER_FOR_ABSPOS;
+ return &mAbsoluteItems;
+ }
+ if (disp->mPosition == NS_STYLE_POSITION_FIXED) {
+ *aPlaceholderType = PLACEHOLDER_FOR_FIXEDPOS;
+ return &GetFixedItems();
+ }
+ }
+ return nullptr;
+}
+
+void
+nsFrameConstructorState::ConstructBackdropFrameFor(nsIContent* aContent,
+ nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_TOP);
+ nsContainerFrame* frame = do_QueryFrame(aFrame);
+ if (!frame) {
+ NS_WARNING("Cannot create backdrop frame for non-container frame");
+ return;
+ }
+
+ RefPtr<nsStyleContext> style = mPresShell->StyleSet()->
+ ResolvePseudoElementStyle(aContent->AsElement(),
+ CSSPseudoElementType::backdrop,
+ /* aParentStyleContext */ nullptr,
+ /* aPseudoElement */ nullptr);
+ MOZ_ASSERT(style->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_TOP);
+ nsContainerFrame* parentFrame =
+ GetGeometricParent(style->StyleDisplay(), nullptr);
+
+ nsBackdropFrame* backdropFrame = new (mPresShell) nsBackdropFrame(style);
+ backdropFrame->Init(aContent, parentFrame, nullptr);
+
+ nsFrameState placeholderType;
+ nsAbsoluteItems* frameItems = GetOutOfFlowFrameItems(backdropFrame,
+ true, true, false,
+ &placeholderType);
+ MOZ_ASSERT(placeholderType == PLACEHOLDER_FOR_TOPLAYER);
+
+ nsIFrame* placeholder = nsCSSFrameConstructor::
+ CreatePlaceholderFrameFor(mPresShell, aContent, backdropFrame,
+ frame->StyleContext(), frame, nullptr,
+ PLACEHOLDER_FOR_TOPLAYER);
+ nsFrameList temp(placeholder, placeholder);
+ frame->SetInitialChildList(nsIFrame::kBackdropList, temp);
+
+ frameItems->AddChild(backdropFrame);
+}
+
+void
+nsFrameConstructorState::AddChild(nsIFrame* aNewFrame,
+ nsFrameItems& aFrameItems,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame* aParentFrame,
+ bool aCanBePositioned,
+ bool aCanBeFloated,
+ bool aIsOutOfFlowPopup,
+ bool aInsertAfter,
+ nsIFrame* aInsertAfterFrame)
+{
+ NS_PRECONDITION(!aNewFrame->GetNextSibling(), "Shouldn't happen");
+
+ nsFrameState placeholderType;
+ nsAbsoluteItems* outOfFlowFrameItems =
+ GetOutOfFlowFrameItems(aNewFrame, aCanBePositioned, aCanBeFloated,
+ aIsOutOfFlowPopup, &placeholderType);
+
+ // The comments in GetGeometricParent regarding root table frames
+ // all apply here, unfortunately. Thus, we need to check whether
+ // the returned frame items really has containing block.
+ nsFrameItems* frameItems;
+ if (outOfFlowFrameItems && outOfFlowFrameItems->containingBlock) {
+ MOZ_ASSERT(aNewFrame->GetParent() == outOfFlowFrameItems->containingBlock,
+ "Parent of the frame is not the containing block?");
+ frameItems = outOfFlowFrameItems;
+ } else {
+ frameItems = &aFrameItems;
+ placeholderType = nsFrameState(0);
+ }
+
+ if (placeholderType) {
+ NS_ASSERTION(frameItems != &aFrameItems,
+ "Putting frame in-flow _and_ want a placeholder?");
+ nsStyleContext* parentContext = aStyleContext->GetParent();
+ nsIFrame* placeholderFrame =
+ nsCSSFrameConstructor::CreatePlaceholderFrameFor(mPresShell,
+ aContent,
+ aNewFrame,
+ parentContext,
+ aParentFrame,
+ nullptr,
+ placeholderType);
+
+ placeholderFrame->AddStateBits(mAdditionalStateBits);
+ // Add the placeholder frame to the flow
+ aFrameItems.AddChild(placeholderFrame);
+
+ if (placeholderType == PLACEHOLDER_FOR_TOPLAYER) {
+ ConstructBackdropFrameFor(aContent, aNewFrame);
+ }
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(aNewFrame->GetParent() == aParentFrame,
+ "In-flow frame has wrong parent");
+ }
+#endif
+
+ if (aInsertAfter) {
+ frameItems->InsertFrame(nullptr, aInsertAfterFrame, aNewFrame);
+ } else {
+ frameItems->AddChild(aNewFrame);
+ }
+}
+
+void
+nsFrameConstructorState::ProcessFrameInsertions(nsAbsoluteItems& aFrameItems,
+ ChildListID aChildListID)
+{
+#define NS_NONXUL_LIST_TEST (&aFrameItems == &mFloatedItems && \
+ aChildListID == nsIFrame::kFloatList) || \
+ ((&aFrameItems == &mAbsoluteItems || \
+ &aFrameItems == &mTopLayerAbsoluteItems) && \
+ aChildListID == nsIFrame::kAbsoluteList) || \
+ ((&aFrameItems == &mFixedItems || \
+ &aFrameItems == &mTopLayerFixedItems) && \
+ aChildListID == nsIFrame::kFixedList)
+#ifdef MOZ_XUL
+ NS_PRECONDITION(NS_NONXUL_LIST_TEST ||
+ (&aFrameItems == &mPopupItems &&
+ aChildListID == nsIFrame::kPopupList),
+ "Unexpected aFrameItems/aChildListID combination");
+#else
+ NS_PRECONDITION(NS_NONXUL_LIST_TEST,
+ "Unexpected aFrameItems/aChildListID combination");
+#endif
+
+ if (aFrameItems.IsEmpty()) {
+ return;
+ }
+
+ nsContainerFrame* containingBlock = aFrameItems.containingBlock;
+
+ NS_ASSERTION(containingBlock,
+ "Child list without containing block?");
+
+ if (aChildListID == nsIFrame::kFixedList) {
+ // Put this frame on the transformed-frame's abs-pos list instead, if
+ // it has abs-pos children instead of fixed-pos children.
+ aChildListID = containingBlock->GetAbsoluteListID();
+ }
+
+ // Insert the frames hanging out in aItems. We can use SetInitialChildList()
+ // if the containing block hasn't been reflowed yet (so NS_FRAME_FIRST_REFLOW
+ // is set) and doesn't have any frames in the aChildListID child list yet.
+ const nsFrameList& childList = containingBlock->GetChildList(aChildListID);
+ if (childList.IsEmpty() &&
+ (containingBlock->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ // If we're injecting absolutely positioned frames, inject them on the
+ // absolute containing block
+ if (aChildListID == containingBlock->GetAbsoluteListID()) {
+ containingBlock->GetAbsoluteContainingBlock()->
+ SetInitialChildList(containingBlock, aChildListID, aFrameItems);
+ } else {
+ containingBlock->SetInitialChildList(aChildListID, aFrameItems);
+ }
+ } else if (aChildListID == nsIFrame::kFixedList ||
+ aChildListID == nsIFrame::kAbsoluteList) {
+ // The order is not important for abs-pos/fixed-pos frame list, just
+ // append the frame items to the list directly.
+ mFrameManager->AppendFrames(containingBlock, aChildListID, aFrameItems);
+ } else {
+ // Note that whether the frame construction context is doing an append or
+ // not is not helpful here, since it could be appending to some frame in
+ // the middle of the document, which means we're not necessarily
+ // appending to the children of the containing block.
+ //
+ // We need to make sure the 'append to the end of document' case is fast.
+ // So first test the last child of the containing block
+ nsIFrame* lastChild = childList.LastChild();
+
+ // CompareTreePosition uses placeholder hierarchy for out of flow frames,
+ // so this will make out-of-flows respect the ordering of placeholders,
+ // which is great because it takes care of anonymous content.
+ nsIFrame* firstNewFrame = aFrameItems.FirstChild();
+
+ // Cache the ancestor chain so that we can reuse it if needed.
+ AutoTArray<nsIFrame*, 20> firstNewFrameAncestors;
+ nsIFrame* notCommonAncestor = nullptr;
+ if (lastChild) {
+ notCommonAncestor = nsLayoutUtils::FillAncestors(firstNewFrame,
+ containingBlock,
+ &firstNewFrameAncestors);
+ }
+
+ if (!lastChild ||
+ nsLayoutUtils::CompareTreePosition(lastChild, firstNewFrame,
+ firstNewFrameAncestors,
+ notCommonAncestor ?
+ containingBlock : nullptr) < 0) {
+ // no lastChild, or lastChild comes before the new children, so just append
+ mFrameManager->AppendFrames(containingBlock, aChildListID, aFrameItems);
+ } else {
+ // Try the other children. First collect them to an array so that a
+ // reasonable fast binary search can be used to find the insertion point.
+ AutoTArray<nsIFrame*, 128> children;
+ for (nsIFrame* f = childList.FirstChild(); f != lastChild;
+ f = f->GetNextSibling()) {
+ children.AppendElement(f);
+ }
+
+ nsIFrame* insertionPoint = nullptr;
+ int32_t imin = 0;
+ int32_t max = children.Length();
+ while (max > imin) {
+ int32_t imid = imin + ((max - imin) / 2);
+ nsIFrame* f = children[imid];
+ int32_t compare =
+ nsLayoutUtils::CompareTreePosition(f, firstNewFrame, firstNewFrameAncestors,
+ notCommonAncestor ? containingBlock : nullptr);
+ if (compare > 0) {
+ // f is after the new frame.
+ max = imid;
+ insertionPoint = imid > 0 ? children[imid - 1] : nullptr;
+ } else if (compare < 0) {
+ // f is before the new frame.
+ imin = imid + 1;
+ insertionPoint = f;
+ } else {
+ // This is for the old behavior. Should be removed once it is
+ // guaranteed that CompareTreePosition can't return 0!
+ // See bug 928645.
+ NS_WARNING("Something odd happening???");
+ insertionPoint = nullptr;
+ for (uint32_t i = 0; i < children.Length(); ++i) {
+ nsIFrame* f = children[i];
+ if (nsLayoutUtils::CompareTreePosition(f, firstNewFrame,
+ firstNewFrameAncestors,
+ notCommonAncestor ?
+ containingBlock : nullptr) > 0) {
+ break;
+ }
+ insertionPoint = f;
+ }
+ break;
+ }
+ }
+ mFrameManager->InsertFrames(containingBlock, aChildListID,
+ insertionPoint, aFrameItems);
+ }
+ }
+
+ NS_POSTCONDITION(aFrameItems.IsEmpty(), "How did that happen?");
+}
+
+
+nsFrameConstructorSaveState::nsFrameConstructorSaveState()
+ : mItems(nullptr),
+ mSavedItems(nullptr),
+ mChildListID(kPrincipalList),
+ mState(nullptr),
+ mSavedFixedItems(nullptr),
+ mSavedFixedPosIsAbsPos(false)
+{
+}
+
+nsFrameConstructorSaveState::~nsFrameConstructorSaveState()
+{
+ // Restore the state
+ if (mItems) {
+ NS_ASSERTION(mState, "Can't have mItems set without having a state!");
+ mState->ProcessFrameInsertions(*mItems, mChildListID);
+ *mItems = mSavedItems;
+#ifdef DEBUG
+ // We've transferred the child list, so drop the pointer we held to it.
+ // Note that this only matters for the assert in ~nsAbsoluteItems.
+ mSavedItems.Clear();
+#endif
+ if (mItems == &mState->mAbsoluteItems) {
+ mState->mFixedPosIsAbsPos = mSavedFixedPosIsAbsPos;
+ if (mSavedFixedPosIsAbsPos) {
+ // mAbsoluteItems was moved to mFixedItems, so move mFixedItems back
+ // and repair the old mFixedItems now.
+ mState->mAbsoluteItems = mState->mFixedItems;
+ mState->mFixedItems = mSavedFixedItems;
+#ifdef DEBUG
+ mSavedFixedItems.Clear();
+#endif
+ }
+ }
+ NS_ASSERTION(!mItems->LastChild() || !mItems->LastChild()->GetNextSibling(),
+ "Something corrupted our list");
+ }
+}
+
+/**
+ * Moves aFrameList from aOldParent to aNewParent. This updates the parent
+ * pointer of the frames in the list, and reparents their views as needed.
+ * nsFrame::SetParent sets the NS_FRAME_HAS_VIEW bit on aNewParent and its
+ * ancestors as needed. Then it sets the list as the initial child list
+ * on aNewParent, unless aNewParent either already has kids or has been
+ * reflowed; in that case it appends the new frames. Note that this
+ * method differs from ReparentFrames in that it doesn't change the kids'
+ * style contexts.
+ */
+// XXXbz Since this is only used for {ib} splits, could we just copy the view
+// bits from aOldParent to aNewParent and then use the
+// nsFrameList::ApplySetParent? That would still leave us doing two passes
+// over the list, of course; if we really wanted to we could factor out the
+// relevant part of ReparentFrameViewList, I suppose... Or just get rid of
+// views, which would make most of this function go away.
+static void
+MoveChildrenTo(nsIFrame* aOldParent,
+ nsContainerFrame* aNewParent,
+ nsFrameList& aFrameList)
+{
+ bool sameGrandParent = aOldParent->GetParent() == aNewParent->GetParent();
+
+ if (aNewParent->HasView() || aOldParent->HasView() || !sameGrandParent) {
+ // Move the frames into the new view
+ nsContainerFrame::ReparentFrameViewList(aFrameList, aOldParent, aNewParent);
+ }
+
+ for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+ e.get()->SetParent(aNewParent);
+ }
+
+ if (aNewParent->PrincipalChildList().IsEmpty() &&
+ (aNewParent->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ aNewParent->SetInitialChildList(kPrincipalList, aFrameList);
+ } else {
+ aNewParent->AppendFrames(kPrincipalList, aFrameList);
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsCSSFrameConstructor::nsCSSFrameConstructor(nsIDocument* aDocument,
+ nsIPresShell* aPresShell)
+ : nsFrameManager(aPresShell)
+ , mDocument(aDocument)
+ , mRootElementFrame(nullptr)
+ , mRootElementStyleFrame(nullptr)
+ , mDocElementContainingBlock(nullptr)
+ , mGfxScrollFrame(nullptr)
+ , mPageSequenceFrame(nullptr)
+ , mCurrentDepth(0)
+#ifdef DEBUG
+ , mUpdateCount(0)
+#endif
+ , mQuotesDirty(false)
+ , mCountersDirty(false)
+ , mIsDestroyingFrameTree(false)
+ , mHasRootAbsPosContainingBlock(false)
+ , mAlwaysCreateFramesForIgnorableWhitespace(false)
+{
+#ifdef DEBUG
+ static bool gFirstTime = true;
+ if (gFirstTime) {
+ gFirstTime = false;
+ char* flags = PR_GetEnv("GECKO_FRAMECTOR_DEBUG_FLAGS");
+ if (flags) {
+ bool error = false;
+ for (;;) {
+ char* comma = PL_strchr(flags, ',');
+ if (comma)
+ *comma = '\0';
+
+ bool found = false;
+ FrameCtorDebugFlags* flag = gFlags;
+ FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
+ while (flag < limit) {
+ if (PL_strcasecmp(flag->name, flags) == 0) {
+ *(flag->on) = true;
+ printf("nsCSSFrameConstructor: setting %s debug flag on\n", flag->name);
+ found = true;
+ break;
+ }
+ ++flag;
+ }
+
+ if (! found)
+ error = true;
+
+ if (! comma)
+ break;
+
+ *comma = ',';
+ flags = comma + 1;
+ }
+
+ if (error) {
+ printf("Here are the available GECKO_FRAMECTOR_DEBUG_FLAGS:\n");
+ FrameCtorDebugFlags* flag = gFlags;
+ FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
+ while (flag < limit) {
+ printf(" %s\n", flag->name);
+ ++flag;
+ }
+ printf("Note: GECKO_FRAMECTOR_DEBUG_FLAGS is a comma separated list of flag\n");
+ printf("names (no whitespace)\n");
+ }
+ }
+ }
+#endif
+}
+
+void
+nsCSSFrameConstructor::NotifyDestroyingFrame(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(mUpdateCount != 0,
+ "Should be in an update while destroying frames");
+
+ if (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT) {
+ if (mQuoteList.DestroyNodesFor(aFrame))
+ QuotesDirty();
+ }
+
+ if (mCounterManager.DestroyNodesFor(aFrame)) {
+ // Technically we don't need to update anything if we destroyed only
+ // USE nodes. However, this is unlikely to happen in the real world
+ // since USE nodes generally go along with INCREMENT nodes.
+ CountersDirty();
+ }
+
+ RestyleManager()->NotifyDestroyingFrame(aFrame);
+
+ nsFrameManager::NotifyDestroyingFrame(aFrame);
+}
+
+struct nsGenConInitializer {
+ nsAutoPtr<nsGenConNode> mNode;
+ nsGenConList* mList;
+ void (nsCSSFrameConstructor::*mDirtyAll)();
+
+ nsGenConInitializer(nsGenConNode* aNode, nsGenConList* aList,
+ void (nsCSSFrameConstructor::*aDirtyAll)())
+ : mNode(aNode), mList(aList), mDirtyAll(aDirtyAll) {}
+};
+
+already_AddRefed<nsIContent>
+nsCSSFrameConstructor::CreateGenConTextNode(nsFrameConstructorState& aState,
+ const nsString& aString,
+ RefPtr<nsTextNode>* aText,
+ nsGenConInitializer* aInitializer)
+{
+ RefPtr<nsTextNode> content = new nsTextNode(mDocument->NodeInfoManager());
+ content->SetText(aString, false);
+ if (aText) {
+ *aText = content;
+ }
+ if (aInitializer) {
+ content->SetProperty(nsGkAtoms::genConInitializerProperty, aInitializer,
+ nsINode::DeleteProperty<nsGenConInitializer>);
+ aState.mGeneratedTextNodesWithInitializer.AppendObject(content);
+ }
+ return content.forget();
+}
+
+already_AddRefed<nsIContent>
+nsCSSFrameConstructor::CreateGeneratedContent(nsFrameConstructorState& aState,
+ nsIContent* aParentContent,
+ nsStyleContext* aStyleContext,
+ uint32_t aContentIndex)
+{
+ // Get the content value
+ const nsStyleContentData &data =
+ aStyleContext->StyleContent()->ContentAt(aContentIndex);
+ nsStyleContentType type = data.mType;
+
+ if (eStyleContentType_Image == type) {
+ if (!data.mContent.mImage) {
+ // CSS had something specified that couldn't be converted to an
+ // image object
+ return nullptr;
+ }
+
+ // Create an image content object and pass it the image request.
+ // XXX Check if it's an image type we can handle...
+
+ RefPtr<NodeInfo> nodeInfo;
+ nodeInfo = mDocument->NodeInfoManager()->
+ GetNodeInfo(nsGkAtoms::mozgeneratedcontentimage, nullptr,
+ kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE);
+
+ nsCOMPtr<nsIContent> content;
+ NS_NewGenConImageContent(getter_AddRefs(content), nodeInfo.forget(),
+ data.mContent.mImage);
+ return content.forget();
+ }
+
+ switch (type) {
+ case eStyleContentType_String:
+ return CreateGenConTextNode(aState,
+ nsDependentString(data.mContent.mString),
+ nullptr, nullptr);
+
+ case eStyleContentType_Attr:
+ {
+ nsCOMPtr<nsIAtom> attrName;
+ int32_t attrNameSpace = kNameSpaceID_None;
+ nsAutoString contentString(data.mContent.mString);
+
+ int32_t barIndex = contentString.FindChar('|'); // CSS namespace delimiter
+ if (-1 != barIndex) {
+ nsAutoString nameSpaceVal;
+ contentString.Left(nameSpaceVal, barIndex);
+ nsresult error;
+ attrNameSpace = nameSpaceVal.ToInteger(&error);
+ contentString.Cut(0, barIndex + 1);
+ if (contentString.Length()) {
+ if (mDocument->IsHTMLDocument() && aParentContent->IsHTMLElement()) {
+ ToLowerCase(contentString);
+ }
+ attrName = NS_Atomize(contentString);
+ }
+ }
+ else {
+ if (mDocument->IsHTMLDocument() && aParentContent->IsHTMLElement()) {
+ ToLowerCase(contentString);
+ }
+ attrName = NS_Atomize(contentString);
+ }
+
+ if (!attrName) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(),
+ attrNameSpace, attrName, getter_AddRefs(content));
+ return content.forget();
+ }
+
+ case eStyleContentType_Counter:
+ case eStyleContentType_Counters:
+ {
+ nsCSSValue::Array* counters = data.mContent.mCounters;
+ nsCounterList* counterList = mCounterManager.CounterListFor(
+ nsDependentString(counters->Item(0).GetStringBufferValue()));
+
+ nsCounterUseNode* node =
+ new nsCounterUseNode(mPresShell->GetPresContext(),
+ counters, aContentIndex,
+ type == eStyleContentType_Counters);
+
+ nsGenConInitializer* initializer =
+ new nsGenConInitializer(node, counterList,
+ &nsCSSFrameConstructor::CountersDirty);
+ return CreateGenConTextNode(aState, EmptyString(), &node->mText,
+ initializer);
+ }
+
+ case eStyleContentType_Image:
+ NS_NOTREACHED("handled by if above");
+ return nullptr;
+
+ case eStyleContentType_OpenQuote:
+ case eStyleContentType_CloseQuote:
+ case eStyleContentType_NoOpenQuote:
+ case eStyleContentType_NoCloseQuote:
+ {
+ nsQuoteNode* node =
+ new nsQuoteNode(type, aContentIndex);
+
+ nsGenConInitializer* initializer =
+ new nsGenConInitializer(node, &mQuoteList,
+ &nsCSSFrameConstructor::QuotesDirty);
+ return CreateGenConTextNode(aState, EmptyString(), &node->mText,
+ initializer);
+ }
+
+ case eStyleContentType_AltContent:
+ {
+ // Use the "alt" attribute; if that fails and the node is an HTML
+ // <input>, try the value attribute and then fall back to some default
+ // localized text we have.
+ // XXX what if the 'alt' attribute is added later, how will we
+ // detect that and do the right thing here?
+ if (aParentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(),
+ kNameSpaceID_None, nsGkAtoms::alt, getter_AddRefs(content));
+ return content.forget();
+ }
+
+ if (aParentContent->IsHTMLElement() &&
+ aParentContent->NodeInfo()->Equals(nsGkAtoms::input)) {
+ if (aParentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(),
+ kNameSpaceID_None, nsGkAtoms::value, getter_AddRefs(content));
+ return content.forget();
+ }
+
+ nsXPIDLString temp;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "Submit", temp);
+ return CreateGenConTextNode(aState, temp, nullptr, nullptr);
+ }
+
+ break;
+ }
+
+ case eStyleContentType_Uninitialized:
+ NS_NOTREACHED("uninitialized content type");
+ return nullptr;
+ } // switch
+
+ return nullptr;
+}
+
+/*
+ * aParentFrame - the frame that should be the parent of the generated
+ * content. This is the frame for the corresponding content node,
+ * which must not be a leaf frame.
+ *
+ * Any items created are added to aItems.
+ *
+ * We create an XML element (tag _moz_generated_content_before or
+ * _moz_generated_content_after) representing the pseudoelement. We
+ * create a DOM node for each 'content' item and make those nodes the
+ * children of the XML element. Then we create a frame subtree for
+ * the XML element as if it were a regular child of
+ * aParentFrame/aParentContent, giving the XML element the ::before or
+ * ::after style.
+ */
+void
+nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aParentContent,
+ nsStyleContext* aStyleContext,
+ CSSPseudoElementType aPseudoElement,
+ FrameConstructionItemList& aItems)
+{
+ MOZ_ASSERT(aPseudoElement == CSSPseudoElementType::before ||
+ aPseudoElement == CSSPseudoElementType::after,
+ "unexpected aPseudoElement");
+
+ // XXXbz is this ever true?
+ if (!aParentContent->IsElement()) {
+ NS_ERROR("Bogus generated content parent");
+ return;
+ }
+
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+
+ // Probe for the existence of the pseudo-element
+ RefPtr<nsStyleContext> pseudoStyleContext;
+ pseudoStyleContext =
+ styleSet->ProbePseudoElementStyle(aParentContent->AsElement(),
+ aPseudoElement,
+ aStyleContext,
+ aState.mTreeMatchContext);
+ if (!pseudoStyleContext)
+ return;
+
+ bool isBefore = aPseudoElement == CSSPseudoElementType::before;
+
+ // |ProbePseudoStyleFor| checked the 'display' property and the
+ // |ContentCount()| of the 'content' property for us.
+ RefPtr<NodeInfo> nodeInfo;
+ nsIAtom* elemName = isBefore ?
+ nsGkAtoms::mozgeneratedcontentbefore : nsGkAtoms::mozgeneratedcontentafter;
+ nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(elemName, nullptr,
+ kNameSpaceID_None,
+ nsIDOMNode::ELEMENT_NODE);
+ nsCOMPtr<Element> container;
+ nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
+ if (NS_FAILED(rv))
+ return;
+ container->SetIsNativeAnonymousRoot();
+
+ // If the parent is in a shadow tree, make sure we don't
+ // bind with a document because shadow roots and its descendants
+ // are not in document.
+ nsIDocument* bindDocument =
+ aParentContent->HasFlag(NODE_IS_IN_SHADOW_TREE) ? nullptr : mDocument;
+ rv = container->BindToTree(bindDocument, aParentContent, aParentContent, true);
+ if (NS_FAILED(rv)) {
+ container->UnbindFromTree();
+ return;
+ }
+
+ // stylo: ServoRestyleManager does not handle transitions yet, and when it
+ // does it probably won't need to track reframed style contexts to start
+ // transitions correctly.
+ if (mozilla::RestyleManager* geckoRM = RestyleManager()->GetAsGecko()) {
+ RestyleManager::ReframingStyleContexts* rsc =
+ geckoRM->GetReframingStyleContexts();
+ if (rsc) {
+ nsStyleContext* oldStyleContext = rsc->Get(container, aPseudoElement);
+ if (oldStyleContext) {
+ RestyleManager::TryInitiatingTransition(aState.mPresContext,
+ container,
+ oldStyleContext,
+ &pseudoStyleContext);
+ } else {
+ aState.mPresContext->TransitionManager()->
+ PruneCompletedTransitions(container, aPseudoElement,
+ pseudoStyleContext);
+ }
+ }
+ }
+
+ uint32_t contentCount = pseudoStyleContext->StyleContent()->ContentCount();
+ for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
+ nsCOMPtr<nsIContent> content =
+ CreateGeneratedContent(aState, aParentContent, pseudoStyleContext,
+ contentIndex);
+ if (content) {
+ container->AppendChildTo(content, false);
+ }
+ }
+
+ AddFrameConstructionItemsInternal(aState, container, aParentFrame, elemName,
+ kNameSpaceID_None, true,
+ pseudoStyleContext,
+ ITEM_IS_GENERATED_CONTENT, nullptr,
+ aItems);
+}
+
+/****************************************************
+ ** BEGIN TABLE SECTION
+ ****************************************************/
+
+// The term pseudo frame is being used instead of anonymous frame, since anonymous
+// frame has been used elsewhere to refer to frames that have generated content
+
+// Return whether the given frame is a table pseudo-frame. Note that
+// cell-content and table-outer frames have pseudo-types, but are always
+// created, even for non-anonymous cells and tables respectively. So for those
+// we have to examine the cell or table frame to see whether it's a pseudo
+// frame. In particular, a lone table caption will have a table wrapper as its
+// parent, but will also trigger construction of an empty inner table, which
+// will be the one we can examine to see whether the wrapper was a pseudo-frame.
+static bool
+IsTablePseudo(nsIFrame* aFrame)
+{
+ nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo();
+ return pseudoType &&
+ (pseudoType == nsCSSAnonBoxes::table ||
+ pseudoType == nsCSSAnonBoxes::inlineTable ||
+ pseudoType == nsCSSAnonBoxes::tableColGroup ||
+ pseudoType == nsCSSAnonBoxes::tableRowGroup ||
+ pseudoType == nsCSSAnonBoxes::tableRow ||
+ pseudoType == nsCSSAnonBoxes::tableCell ||
+ (pseudoType == nsCSSAnonBoxes::cellContent &&
+ aFrame->GetParent()->StyleContext()->GetPseudo() ==
+ nsCSSAnonBoxes::tableCell) ||
+ (pseudoType == nsCSSAnonBoxes::tableWrapper &&
+ (aFrame->PrincipalChildList().FirstChild()->StyleContext()->GetPseudo() ==
+ nsCSSAnonBoxes::table ||
+ aFrame->PrincipalChildList().FirstChild()->StyleContext()->GetPseudo() ==
+ nsCSSAnonBoxes::inlineTable)));
+}
+
+static bool
+IsRubyPseudo(nsIFrame* aFrame)
+{
+ return RubyUtils::IsRubyPseudo(aFrame->StyleContext()->GetPseudo());
+}
+
+static bool
+IsTableOrRubyPseudo(nsIFrame* aFrame)
+{
+ return IsTablePseudo(aFrame) || IsRubyPseudo(aFrame);
+}
+
+/* static */
+nsCSSFrameConstructor::ParentType
+nsCSSFrameConstructor::GetParentType(nsIAtom* aFrameType)
+{
+ if (aFrameType == nsGkAtoms::tableFrame) {
+ return eTypeTable;
+ }
+ if (aFrameType == nsGkAtoms::tableRowGroupFrame) {
+ return eTypeRowGroup;
+ }
+ if (aFrameType == nsGkAtoms::tableRowFrame) {
+ return eTypeRow;
+ }
+ if (aFrameType == nsGkAtoms::tableColGroupFrame) {
+ return eTypeColGroup;
+ }
+ if (aFrameType == nsGkAtoms::rubyBaseContainerFrame) {
+ return eTypeRubyBaseContainer;
+ }
+ if (aFrameType == nsGkAtoms::rubyTextContainerFrame) {
+ return eTypeRubyTextContainer;
+ }
+ if (aFrameType == nsGkAtoms::rubyFrame) {
+ return eTypeRuby;
+ }
+
+ return eTypeBlock;
+}
+
+static nsContainerFrame*
+AdjustCaptionParentFrame(nsContainerFrame* aParentFrame)
+{
+ if (nsGkAtoms::tableFrame == aParentFrame->GetType()) {
+ return aParentFrame->GetParent();
+ }
+ return aParentFrame;
+}
+
+/**
+ * If the parent frame is a |tableFrame| and the child is a
+ * |captionFrame|, then we want to insert the frames beneath the
+ * |tableFrame|'s parent frame. Returns |true| if the parent frame
+ * needed to be fixed up.
+ */
+static bool
+GetCaptionAdjustedParent(nsContainerFrame* aParentFrame,
+ const nsIFrame* aChildFrame,
+ nsContainerFrame** aAdjParentFrame)
+{
+ *aAdjParentFrame = aParentFrame;
+ bool haveCaption = false;
+
+ if (aChildFrame->IsTableCaption()) {
+ haveCaption = true;
+ *aAdjParentFrame = ::AdjustCaptionParentFrame(aParentFrame);
+ }
+ return haveCaption;
+}
+
+void
+nsCSSFrameConstructor::AdjustParentFrame(nsContainerFrame** aParentFrame,
+ const FrameConstructionData* aFCData,
+ nsStyleContext* aStyleContext)
+{
+ NS_PRECONDITION(aStyleContext, "Must have child's style context");
+ NS_PRECONDITION(aFCData, "Must have frame construction data");
+
+ bool tablePart = ((aFCData->mBits & FCDATA_IS_TABLE_PART) != 0);
+
+ if (tablePart && aStyleContext->StyleDisplay()->mDisplay ==
+ StyleDisplay::TableCaption) {
+ *aParentFrame = ::AdjustCaptionParentFrame(*aParentFrame);
+ }
+}
+
+// Pull all the captions present in aItems out into aCaptions
+static void
+PullOutCaptionFrames(nsFrameItems& aItems, nsFrameItems& aCaptions)
+{
+ nsIFrame* child = aItems.FirstChild();
+ while (child) {
+ nsIFrame* nextSibling = child->GetNextSibling();
+ if (child->IsTableCaption()) {
+ aItems.RemoveFrame(child);
+ aCaptions.AddChild(child);
+ }
+ child = nextSibling;
+ }
+}
+
+
+// Construct the outer, inner table frames and the children frames for the table.
+// XXX Page break frames for pseudo table frames are not constructed to avoid the risk
+// associated with revising the pseudo frame mechanism. The long term solution
+// of having frames handle page-break-before/after will solve the problem.
+nsIFrame*
+nsCSSFrameConstructor::ConstructTable(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ NS_PRECONDITION(aDisplay->mDisplay == StyleDisplay::Table ||
+ aDisplay->mDisplay == StyleDisplay::InlineTable,
+ "Unexpected call");
+
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+ const uint32_t nameSpaceID = aItem.mNameSpaceID;
+
+ // create the pseudo SC for the table wrapper as a child of the inner SC
+ RefPtr<nsStyleContext> outerStyleContext;
+ outerStyleContext = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableWrapper, styleContext);
+
+ // Create the table wrapper frame which holds the caption and inner table frame
+ nsContainerFrame* newFrame;
+ if (kNameSpaceID_MathML == nameSpaceID)
+ newFrame = NS_NewMathMLmtableOuterFrame(mPresShell, outerStyleContext);
+ else
+ newFrame = NS_NewTableWrapperFrame(mPresShell, outerStyleContext);
+
+ nsContainerFrame* geometricParent =
+ aState.GetGeometricParent(outerStyleContext->StyleDisplay(),
+ aParentFrame);
+
+ // Init the table wrapper frame
+ InitAndRestoreFrame(aState, content, geometricParent, newFrame);
+
+ // Create the inner table frame
+ nsContainerFrame* innerFrame;
+ if (kNameSpaceID_MathML == nameSpaceID)
+ innerFrame = NS_NewMathMLmtableFrame(mPresShell, styleContext);
+ else
+ innerFrame = NS_NewTableFrame(mPresShell, styleContext);
+
+ InitAndRestoreFrame(aState, content, newFrame, innerFrame);
+
+ // Put the newly created frames into the right child list
+ SetInitialSingleChild(newFrame, innerFrame);
+
+ aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
+
+ if (!mRootElementFrame) {
+ // The frame we're constructing will be the root element frame.
+ // Set mRootElementFrame before processing children.
+ mRootElementFrame = newFrame;
+ }
+
+ nsFrameItems childItems;
+
+ // Process children
+ nsFrameConstructorSaveState absoluteSaveState;
+ const nsStyleDisplay* display = outerStyleContext->StyleDisplay();
+
+ // Mark the table frame as an absolute container if needed
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (display->IsAbsPosContainingBlock(newFrame)) {
+ aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
+ }
+ NS_ASSERTION(aItem.mAnonChildren.IsEmpty(),
+ "nsIAnonymousContentCreator::CreateAnonymousContent "
+ "implementations for table frames are not currently expected "
+ "to output a list where the items have their own children");
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(aState, aItem.mChildItems,
+ innerFrame, childItems);
+ } else {
+ ProcessChildren(aState, content, styleContext, innerFrame,
+ true, childItems, false, aItem.mPendingBinding);
+ }
+
+ nsFrameItems captionItems;
+ PullOutCaptionFrames(childItems, captionItems);
+
+ // Set the inner table frame's initial primary list
+ innerFrame->SetInitialChildList(kPrincipalList, childItems);
+
+ // Set the table wrapper frame's secondary childlist lists
+ if (captionItems.NotEmpty()) {
+ newFrame->SetInitialChildList(nsIFrame::kCaptionList, captionItems);
+ }
+
+ return newFrame;
+}
+
+static void
+MakeTablePartAbsoluteContainingBlockIfNeeded(nsFrameConstructorState& aState,
+ const nsStyleDisplay* aDisplay,
+ nsFrameConstructorSaveState& aAbsSaveState,
+ nsContainerFrame* aFrame)
+{
+ // If we're positioned, then we need to become an absolute containing block
+ // for any absolutely positioned children and register for post-reflow fixup.
+ //
+ // Note that usually if a frame type can be an absolute containing block, we
+ // always set NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN, whether it actually is or not.
+ // However, in this case flag serves the additional purpose of indicating that
+ // the frame was registered with its table frame. This allows us to avoid the
+ // overhead of unregistering the frame in most cases.
+ if (aDisplay->IsAbsPosContainingBlock(aFrame)) {
+ aFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ aState.PushAbsoluteContainingBlock(aFrame, aFrame, aAbsSaveState);
+ nsTableFrame::RegisterPositionedTablePart(aFrame);
+ }
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructTableRowOrRowGroup(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableRow ||
+ aDisplay->mDisplay == StyleDisplay::TableRowGroup ||
+ aDisplay->mDisplay == StyleDisplay::TableFooterGroup ||
+ aDisplay->mDisplay == StyleDisplay::TableHeaderGroup,
+ "Not a row or row group");
+ MOZ_ASSERT(aItem.mStyleContext->StyleDisplay() == aDisplay,
+ "Display style doesn't match style context");
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+ const uint32_t nameSpaceID = aItem.mNameSpaceID;
+
+ nsContainerFrame* newFrame;
+ if (aDisplay->mDisplay == StyleDisplay::TableRow) {
+ if (kNameSpaceID_MathML == nameSpaceID)
+ newFrame = NS_NewMathMLmtrFrame(mPresShell, styleContext);
+ else
+ newFrame = NS_NewTableRowFrame(mPresShell, styleContext);
+ } else {
+ newFrame = NS_NewTableRowGroupFrame(mPresShell, styleContext);
+ }
+
+ InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlockIfNeeded(aState, aDisplay,
+ absoluteSaveState,
+ newFrame);
+
+ nsFrameItems childItems;
+ NS_ASSERTION(aItem.mAnonChildren.IsEmpty(),
+ "nsIAnonymousContentCreator::CreateAnonymousContent "
+ "implementations for table frames are not currently expected "
+ "to output a list where the items have their own children");
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(aState, aItem.mChildItems, newFrame,
+ childItems);
+ } else {
+ ProcessChildren(aState, content, styleContext, newFrame,
+ true, childItems, false, aItem.mPendingBinding);
+ }
+
+ newFrame->SetInitialChildList(kPrincipalList, childItems);
+ aFrameItems.AddChild(newFrame);
+ return newFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructTableCol(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems)
+{
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ nsTableColFrame* colFrame = NS_NewTableColFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(aState, content, aParentFrame, colFrame);
+
+ NS_ASSERTION(colFrame->StyleContext() == styleContext,
+ "Unexpected style context");
+
+ aFrameItems.AddChild(colFrame);
+
+ // construct additional col frames if the col frame has a span > 1
+ int32_t span = colFrame->GetSpan();
+ for (int32_t spanX = 1; spanX < span; spanX++) {
+ nsTableColFrame* newCol = NS_NewTableColFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(aState, content, aParentFrame, newCol, false);
+ aFrameItems.LastChild()->SetNextContinuation(newCol);
+ newCol->SetPrevContinuation(aFrameItems.LastChild());
+ aFrameItems.AddChild(newCol);
+ newCol->SetColType(eColAnonymousCol);
+ }
+
+ return colFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructTableCell(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableCell,
+ "Unexpected call");
+
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+ const uint32_t nameSpaceID = aItem.mNameSpaceID;
+
+ nsTableFrame* tableFrame =
+ static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
+ nsContainerFrame* newFrame;
+ // <mtable> is border separate in mathml.css and the MathML code doesn't implement
+ // border collapse. For those users who style <mtable> with border collapse,
+ // give them the default non-MathML table frames that understand border collapse.
+ // This won't break us because MathML table frames are all subclasses of the default
+ // table code, and so we can freely mix <mtable> with <mtr> or <tr>, <mtd> or <td>.
+ // What will happen is just that non-MathML frames won't understand MathML attributes
+ // and will therefore miss the special handling that the MathML code does.
+ if (kNameSpaceID_MathML == nameSpaceID && !tableFrame->IsBorderCollapse()) {
+ newFrame = NS_NewMathMLmtdFrame(mPresShell, styleContext, tableFrame);
+ } else {
+ // Warning: If you change this and add a wrapper frame around table cell
+ // frames, make sure Bug 368554 doesn't regress!
+ // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
+ newFrame = NS_NewTableCellFrame(mPresShell, styleContext, tableFrame);
+ }
+
+ // Initialize the table cell frame
+ InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+
+ // Resolve pseudo style and initialize the body cell frame
+ RefPtr<nsStyleContext> innerPseudoStyle;
+ innerPseudoStyle = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::cellContent, styleContext);
+
+ // Create a block frame that will format the cell's content
+ bool isBlock;
+ nsContainerFrame* cellInnerFrame;
+ if (kNameSpaceID_MathML == nameSpaceID) {
+ cellInnerFrame = NS_NewMathMLmtdInnerFrame(mPresShell, innerPseudoStyle);
+ isBlock = false;
+ } else {
+ cellInnerFrame = NS_NewBlockFormattingContext(mPresShell, innerPseudoStyle);
+ isBlock = true;
+ }
+
+ InitAndRestoreFrame(aState, content, newFrame, cellInnerFrame);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlockIfNeeded(aState, aDisplay,
+ absoluteSaveState,
+ newFrame);
+
+ nsFrameItems childItems;
+ NS_ASSERTION(aItem.mAnonChildren.IsEmpty(),
+ "nsIAnonymousContentCreator::CreateAnonymousContent "
+ "implementations for table frames are not currently expected "
+ "to output a list where the items have their own children");
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ // Need to push ourselves as a float containing block.
+ // XXXbz it might be nice to work on getting the parent
+ // FrameConstructionItem down into ProcessChildren and just making use of
+ // the push there, but that's a bit of work.
+ nsFrameConstructorSaveState floatSaveState;
+ if (!isBlock) { /* MathML case */
+ aState.PushFloatContainingBlock(nullptr, floatSaveState);
+ } else {
+ aState.PushFloatContainingBlock(cellInnerFrame, floatSaveState);
+ }
+
+ ConstructFramesFromItemList(aState, aItem.mChildItems, cellInnerFrame,
+ childItems);
+ } else {
+ // Process the child content
+ ProcessChildren(aState, content, styleContext, cellInnerFrame,
+ true, childItems, isBlock, aItem.mPendingBinding);
+ }
+
+ cellInnerFrame->SetInitialChildList(kPrincipalList, childItems);
+ SetInitialSingleChild(newFrame, cellInnerFrame);
+ aFrameItems.AddChild(newFrame);
+ return newFrame;
+}
+
+static inline bool
+NeedFrameFor(const nsFrameConstructorState& aState,
+ nsIFrame* aParentFrame,
+ nsIContent* aChildContent)
+{
+ // XXX the GetContent() != aChildContent check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ NS_PRECONDITION(!aChildContent->GetPrimaryFrame() ||
+ aState.mCreatingExtraFrames ||
+ aChildContent->GetPrimaryFrame()->GetContent() != aChildContent,
+ "Why did we get called?");
+
+ // don't create a whitespace frame if aParentFrame doesn't want it.
+ // always create frames for children in generated content. counter(),
+ // quotes, and attr() content can easily change dynamically and we don't
+ // want to be reconstructing frames. It's not even clear that these
+ // should be considered ignorable just because they evaluate to
+ // whitespace.
+
+ // We could handle all this in CreateNeededPseudoContainers or some other
+ // place after we build our frame construction items, but that would involve
+ // creating frame construction items for whitespace kids of
+ // eExcludesIgnorableWhitespace frames, where we know we'll be dropping them
+ // all anyway, and involve an extra walk down the frame construction item
+ // list.
+ if ((aParentFrame &&
+ (!aParentFrame->IsFrameOfType(nsIFrame::eExcludesIgnorableWhitespace) ||
+ aParentFrame->IsGeneratedContentFrame())) ||
+ !aChildContent->IsNodeOfType(nsINode::eTEXT)) {
+ return true;
+ }
+
+ aChildContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
+ NS_REFRAME_IF_WHITESPACE);
+ return !aChildContent->TextIsOnlyWhitespace();
+}
+
+/***********************************************
+ * END TABLE SECTION
+ ***********************************************/
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructDocElementFrame(Element* aDocElement,
+ nsILayoutHistoryState* aFrameState)
+{
+ MOZ_ASSERT(GetRootFrame(),
+ "No viewport? Someone forgot to call ConstructRootFrame!");
+ MOZ_ASSERT(!mDocElementContainingBlock,
+ "Shouldn't have a doc element containing block here");
+
+ // Resolve a new style context for the viewport since it may be affected
+ // by a new root element style (e.g. a propagated 'direction').
+ // @see nsStyleContext::ApplyStyleFixups
+ {
+ RefPtr<nsStyleContext> sc = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::viewport, nullptr);
+ GetRootFrame()->SetStyleContextWithoutNotification(sc);
+ }
+
+ // Make sure to call UpdateViewportScrollbarStylesOverride before
+ // SetUpDocElementContainingBlock, since it sets up our scrollbar state
+ // properly.
+ DebugOnly<nsIContent*> propagatedScrollFrom;
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ propagatedScrollFrom = presContext->UpdateViewportScrollbarStylesOverride();
+ }
+
+ SetUpDocElementContainingBlock(aDocElement);
+
+ NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
+
+ nsFrameConstructorState state(mPresShell,
+ GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
+ nullptr,
+ nullptr, do_AddRef(aFrameState));
+ // Initialize the ancestor filter with null for now; we'll push
+ // aDocElement once we finish resolving style for it.
+ state.mTreeMatchContext.InitAncestors(nullptr);
+
+ // XXXbz why, exactly?
+ if (!mTempFrameTreeState)
+ state.mPresShell->CaptureHistoryState(getter_AddRefs(mTempFrameTreeState));
+
+ // Make sure that we'll handle restyles for this document element in
+ // the future. We need this, because the document element might
+ // have stale restyle bits from a previous frame constructor for
+ // this document. Unlike in AddFrameConstructionItems, it's safe to
+ // unset all element restyle flags, since we don't have any
+ // siblings.
+ aDocElement->UnsetRestyleFlagsIfGecko();
+
+ // --------- CREATE AREA OR BOX FRAME -------
+ // FIXME: Should this use ResolveStyleContext? (The calls in this
+ // function are the only case in nsCSSFrameConstructor where we don't
+ // do so for the construction of a style context for an element.)
+ RefPtr<nsStyleContext> styleContext;
+ styleContext = mPresShell->StyleSet()->ResolveStyleFor(aDocElement,
+ nullptr);
+
+ const nsStyleDisplay* display = styleContext->StyleDisplay();
+
+ // Ensure that our XBL bindings are installed.
+ if (display->mBinding) {
+ // Get the XBL loader.
+ nsresult rv;
+ bool resolveStyle;
+
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService) {
+ return nullptr;
+ }
+
+ RefPtr<nsXBLBinding> binding;
+ rv = xblService->LoadBindings(aDocElement, display->mBinding->GetURI(),
+ display->mBinding->mOriginPrincipal,
+ getter_AddRefs(binding), &resolveStyle);
+ if (NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED)
+ return nullptr; // Binding will load asynchronously.
+
+ if (binding) {
+ // For backwards compat, keep firing the root's constructor
+ // after all of its kids' constructors. So tell the binding
+ // manager about it right now.
+ mDocument->BindingManager()->AddToAttachedQueue(binding);
+ }
+
+ if (resolveStyle) {
+ // FIXME: Should this use ResolveStyleContext? (The calls in this
+ // function are the only case in nsCSSFrameConstructor where we
+ // don't do so for the construction of a style context for an
+ // element.)
+ styleContext = mPresShell->StyleSet()->ResolveStyleFor(aDocElement,
+ nullptr);
+ display = styleContext->StyleDisplay();
+ }
+ }
+
+ // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
+
+ NS_ASSERTION(!display->IsScrollableOverflow() ||
+ state.mPresContext->IsPaginated() ||
+ propagatedScrollFrom == aDocElement,
+ "Scrollbars should have been propagated to the viewport");
+
+ if (MOZ_UNLIKELY(display->mDisplay == StyleDisplay::None)) {
+ SetUndisplayedContent(aDocElement, styleContext);
+ return nullptr;
+ }
+
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(state.mTreeMatchContext);
+ ancestorPusher.PushAncestorAndStyleScope(aDocElement);
+
+ // Make sure to start any background image loads for the root element now.
+ styleContext->StartBackgroundImageLoads();
+
+ nsFrameConstructorSaveState docElementContainingBlockAbsoluteSaveState;
+ if (mHasRootAbsPosContainingBlock) {
+ // Push the absolute containing block now so we can absolutely position
+ // the root element
+ mDocElementContainingBlock->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ state.PushAbsoluteContainingBlock(mDocElementContainingBlock,
+ mDocElementContainingBlock,
+ docElementContainingBlockAbsoluteSaveState);
+ }
+
+ // The rules from CSS 2.1, section 9.2.4, have already been applied
+ // by the style system, so we can assume that display->mDisplay is
+ // either NONE, BLOCK, or TABLE.
+
+ // contentFrame is the primary frame for the root element. newFrame
+ // is the frame that will be the child of the initial containing block.
+ // These are usually the same frame but they can be different, in
+ // particular if the root frame is positioned, in which case
+ // contentFrame is the out-of-flow frame and newFrame is the
+ // placeholder.
+ nsContainerFrame* contentFrame;
+ nsIFrame* newFrame;
+ bool processChildren = false;
+
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ // Check whether we need to build a XUL box or SVG root frame
+#ifdef MOZ_XUL
+ if (aDocElement->IsXULElement()) {
+ contentFrame = NS_NewDocElementBoxFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(state, aDocElement, mDocElementContainingBlock,
+ contentFrame);
+ newFrame = contentFrame;
+ processChildren = true;
+ }
+ else
+#endif
+ if (aDocElement->IsSVGElement()) {
+ if (!aDocElement->IsSVGElement(nsGkAtoms::svg)) {
+ return nullptr;
+ }
+ // We're going to call the right function ourselves, so no need to give a
+ // function to this FrameConstructionData.
+
+ // XXXbz on the other hand, if we converted this whole function to
+ // FrameConstructionData/Item, then we'd need the right function
+ // here... but would probably be able to get away with less code in this
+ // function in general.
+ // Use a null PendingBinding, since our binding is not in fact pending.
+ static const FrameConstructionData rootSVGData = FCDATA_DECL(0, nullptr);
+ already_AddRefed<nsStyleContext> extraRef =
+ RefPtr<nsStyleContext>(styleContext).forget();
+ FrameConstructionItem item(&rootSVGData, aDocElement,
+ aDocElement->NodeInfo()->NameAtom(),
+ kNameSpaceID_SVG, nullptr, extraRef, true,
+ nullptr);
+
+ nsFrameItems frameItems;
+ contentFrame = static_cast<nsContainerFrame*>(
+ ConstructOuterSVG(state, item, mDocElementContainingBlock,
+ styleContext->StyleDisplay(),
+ frameItems));
+ newFrame = frameItems.FirstChild();
+ NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
+ } else if (display->mDisplay == StyleDisplay::Flex ||
+ display->mDisplay == StyleDisplay::WebkitBox) {
+ contentFrame = NS_NewFlexContainerFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(state, aDocElement, mDocElementContainingBlock,
+ contentFrame);
+ newFrame = contentFrame;
+ processChildren = true;
+
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (display->IsAbsPosContainingBlock(newFrame)) {
+ state.PushAbsoluteContainingBlock(contentFrame, newFrame,
+ absoluteSaveState);
+ }
+
+ } else if (display->mDisplay == StyleDisplay::Grid) {
+ contentFrame = NS_NewGridContainerFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(state, aDocElement, mDocElementContainingBlock,
+ contentFrame);
+ newFrame = contentFrame;
+ processChildren = true;
+
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (display->IsAbsPosContainingBlock(newFrame)) {
+ state.PushAbsoluteContainingBlock(contentFrame, newFrame,
+ absoluteSaveState);
+ }
+ } else if (display->mDisplay == StyleDisplay::Table) {
+ // We're going to call the right function ourselves, so no need to give a
+ // function to this FrameConstructionData.
+
+ // XXXbz on the other hand, if we converted this whole function to
+ // FrameConstructionData/Item, then we'd need the right function
+ // here... but would probably be able to get away with less code in this
+ // function in general.
+ // Use a null PendingBinding, since our binding is not in fact pending.
+ static const FrameConstructionData rootTableData = FCDATA_DECL(0, nullptr);
+ already_AddRefed<nsStyleContext> extraRef =
+ RefPtr<nsStyleContext>(styleContext).forget();
+ FrameConstructionItem item(&rootTableData, aDocElement,
+ aDocElement->NodeInfo()->NameAtom(),
+ kNameSpaceID_None, nullptr, extraRef, true,
+ nullptr);
+
+ nsFrameItems frameItems;
+ // if the document is a table then just populate it.
+ contentFrame = static_cast<nsContainerFrame*>(
+ ConstructTable(state, item, mDocElementContainingBlock,
+ styleContext->StyleDisplay(),
+ frameItems));
+ newFrame = frameItems.FirstChild();
+ NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
+ } else {
+ MOZ_ASSERT(display->mDisplay == StyleDisplay::Block,
+ "Unhandled display type for root element");
+ contentFrame = NS_NewBlockFormattingContext(mPresShell, styleContext);
+ nsFrameItems frameItems;
+ // Use a null PendingBinding, since our binding is not in fact pending.
+ ConstructBlock(state, aDocElement,
+ state.GetGeometricParent(display,
+ mDocElementContainingBlock),
+ mDocElementContainingBlock, styleContext,
+ &contentFrame, frameItems,
+ display->IsAbsPosContainingBlock(contentFrame) ? contentFrame : nullptr,
+ nullptr);
+ newFrame = frameItems.FirstChild();
+ NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
+ }
+
+ MOZ_ASSERT(newFrame);
+ MOZ_ASSERT(contentFrame);
+
+ NS_ASSERTION(processChildren ? !mRootElementFrame :
+ mRootElementFrame == contentFrame,
+ "unexpected mRootElementFrame");
+ mRootElementFrame = contentFrame;
+
+ // Figure out which frame has the main style for the document element,
+ // assigning it to mRootElementStyleFrame.
+ // Backgrounds should be propagated from that frame to the viewport.
+ contentFrame->GetParentStyleContext(&mRootElementStyleFrame);
+ bool isChild = mRootElementStyleFrame &&
+ mRootElementStyleFrame->GetParent() == contentFrame;
+ if (!isChild) {
+ mRootElementStyleFrame = mRootElementFrame;
+ }
+
+ if (processChildren) {
+ // Still need to process the child content
+ nsFrameItems childItems;
+
+ NS_ASSERTION(!nsLayoutUtils::GetAsBlock(contentFrame) &&
+ !contentFrame->IsFrameOfType(nsIFrame::eSVG),
+ "Only XUL frames should reach here");
+ // Use a null PendingBinding, since our binding is not in fact pending.
+ ProcessChildren(state, aDocElement, styleContext, contentFrame, true,
+ childItems, false, nullptr);
+
+ // Set the initial child lists
+ contentFrame->SetInitialChildList(kPrincipalList, childItems);
+ }
+
+ // set the primary frame
+ aDocElement->SetPrimaryFrame(contentFrame);
+
+ SetInitialSingleChild(mDocElementContainingBlock, newFrame);
+
+ // Create frames for anonymous contents if there is a canvas frame.
+ if (mDocElementContainingBlock->GetType() == nsGkAtoms::canvasFrame) {
+ ConstructAnonymousContentForCanvas(state, mDocElementContainingBlock,
+ aDocElement);
+ }
+
+ return newFrame;
+}
+
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructRootFrame()
+{
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+
+ // Set up our style rule observer.
+ // XXXbz wouldn't this make more sense as part of presshell init?
+ if (styleSet->IsGecko()) {
+ // XXXheycam We don't support XBL bindings providing style to
+ // ServoStyleSets yet.
+ styleSet->AsGecko()->SetBindingManager(mDocument->BindingManager());
+ } else {
+ NS_WARNING("stylo: cannot get ServoStyleSheets from XBL bindings yet. See bug 1290276.");
+ }
+
+ // --------- BUILD VIEWPORT -----------
+ RefPtr<nsStyleContext> viewportPseudoStyle =
+ styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::viewport, nullptr);
+ ViewportFrame* viewportFrame =
+ NS_NewViewportFrame(mPresShell, viewportPseudoStyle);
+
+ // XXXbz do we _have_ to pass a null content pointer to that frame?
+ // Would it really kill us to pass in the root element or something?
+ // What would that break?
+ viewportFrame->Init(nullptr, nullptr, nullptr);
+
+ // Bind the viewport frame to the root view
+ nsView* rootView = mPresShell->GetViewManager()->GetRootView();
+ viewportFrame->SetView(rootView);
+
+ nsContainerFrame::SyncFrameViewProperties(mPresShell->GetPresContext(), viewportFrame,
+ viewportPseudoStyle, rootView);
+ nsContainerFrame::SyncWindowProperties(mPresShell->GetPresContext(), viewportFrame,
+ rootView, nullptr, nsContainerFrame::SET_ASYNC);
+
+ // Make it an absolute container for fixed-pos elements
+ viewportFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ viewportFrame->MarkAsAbsoluteContainingBlock();
+
+ return viewportFrame;
+}
+
+void
+nsCSSFrameConstructor::SetUpDocElementContainingBlock(nsIContent* aDocElement)
+{
+ NS_PRECONDITION(aDocElement, "No element?");
+ NS_PRECONDITION(!aDocElement->GetParent(), "Not root content?");
+ NS_PRECONDITION(aDocElement->GetUncomposedDoc(), "Not in a document?");
+ NS_PRECONDITION(aDocElement->GetUncomposedDoc()->GetRootElement() ==
+ aDocElement, "Not the root of the document?");
+
+ /*
+ how the root frame hierarchy should look
+
+ Galley presentation, non-XUL, with scrolling:
+
+ ViewportFrame [fixed-cb]
+ nsHTMLScrollFrame
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, nsSVGOuterSVGFrame,
+ nsTableWrapperFrame, nsPlaceholderFrame)
+
+ Galley presentation, XUL
+
+ ViewportFrame [fixed-cb]
+ nsRootBoxFrame
+ root element frame (nsDocElementBoxFrame)
+
+ Print presentation, non-XUL
+
+ ViewportFrame
+ nsSimplePageSequenceFrame
+ nsPageFrame
+ nsPageContentFrame [fixed-cb]
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, nsSVGOuterSVGFrame,
+ nsTableWrapperFrame, nsPlaceholderFrame)
+
+ Print-preview presentation, non-XUL
+
+ ViewportFrame
+ nsHTMLScrollFrame
+ nsSimplePageSequenceFrame
+ nsPageFrame
+ nsPageContentFrame [fixed-cb]
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, nsSVGOuterSVGFrame,
+ nsTableWrapperFrame, nsPlaceholderFrame)
+
+ Print/print preview of XUL is not supported.
+ [fixed-cb]: the default containing block for fixed-pos content
+ [abs-cb]: the default containing block for abs-pos content
+
+ Meaning of nsCSSFrameConstructor fields:
+ mRootElementFrame is "root element frame". This is the primary frame for
+ the root element.
+ mDocElementContainingBlock is the parent of mRootElementFrame
+ (i.e. nsCanvasFrame or nsRootBoxFrame)
+ mGfxScrollFrame is the nsHTMLScrollFrame mentioned above, or null if there isn't one
+ mPageSequenceFrame is the nsSimplePageSequenceFrame, or null if there isn't one
+ */
+
+ // --------- CREATE ROOT FRAME -------
+
+
+ // Create the root frame. The document element's frame is a child of the
+ // root frame.
+ //
+ // The root frame serves two purposes:
+ // - reserves space for any margins needed for the document element's frame
+ // - renders the document element's background. This ensures the background covers
+ // the entire canvas as specified by the CSS2 spec
+
+ nsPresContext* presContext = mPresShell->GetPresContext();
+ bool isPaginated = presContext->IsRootPaginatedDocument();
+ nsContainerFrame* viewportFrame = static_cast<nsContainerFrame*>(GetRootFrame());
+ nsStyleContext* viewportPseudoStyle = viewportFrame->StyleContext();
+
+ nsContainerFrame* rootFrame = nullptr;
+ nsIAtom* rootPseudo;
+
+ if (!isPaginated) {
+#ifdef MOZ_XUL
+ if (aDocElement->IsXULElement())
+ {
+ // pass a temporary stylecontext, the correct one will be set later
+ rootFrame = NS_NewRootBoxFrame(mPresShell, viewportPseudoStyle);
+ } else
+#endif
+ {
+ // pass a temporary stylecontext, the correct one will be set later
+ rootFrame = NS_NewCanvasFrame(mPresShell, viewportPseudoStyle);
+ mHasRootAbsPosContainingBlock = true;
+ }
+
+ rootPseudo = nsCSSAnonBoxes::canvas;
+ mDocElementContainingBlock = rootFrame;
+ } else {
+ // Create a page sequence frame
+ rootFrame = NS_NewSimplePageSequenceFrame(mPresShell, viewportPseudoStyle);
+ mPageSequenceFrame = rootFrame;
+ rootPseudo = nsCSSAnonBoxes::pageSequence;
+ }
+
+
+ // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
+
+ // If the device supports scrolling (e.g., in galley mode on the screen and
+ // for print-preview, but not when printing), then create a scroll frame that
+ // will act as the scrolling mechanism for the viewport.
+ // XXX Do we even need a viewport when printing to a printer?
+
+ bool isHTML = aDocElement->IsHTMLElement();
+ bool isXUL = false;
+
+ if (!isHTML) {
+ isXUL = aDocElement->IsXULElement();
+ }
+
+ // Never create scrollbars for XUL documents
+ bool isScrollable = isPaginated ? presContext->HasPaginatedScrolling() : !isXUL;
+
+ // We no longer need to do overflow propagation here. It's taken care of
+ // when we construct frames for the element whose overflow might be
+ // propagated
+ NS_ASSERTION(!isScrollable || !isXUL,
+ "XUL documents should never be scrollable - see above");
+
+ nsContainerFrame* newFrame = rootFrame;
+ RefPtr<nsStyleContext> rootPseudoStyle;
+ // we must create a state because if the scrollbars are GFX it needs the
+ // state to build the scrollbar frames.
+ nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+
+ // Start off with the viewport as parent; we'll adjust it as needed.
+ nsContainerFrame* parentFrame = viewportFrame;
+
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+ // If paginated, make sure we don't put scrollbars in
+ if (!isScrollable) {
+ rootPseudoStyle = styleSet->ResolveAnonymousBoxStyle(rootPseudo,
+ viewportPseudoStyle);
+ } else {
+ if (rootPseudo == nsCSSAnonBoxes::canvas) {
+ rootPseudo = nsCSSAnonBoxes::scrolledCanvas;
+ } else {
+ NS_ASSERTION(rootPseudo == nsCSSAnonBoxes::pageSequence,
+ "Unknown root pseudo");
+ rootPseudo = nsCSSAnonBoxes::scrolledPageSequence;
+ }
+
+ // Build the frame. We give it the content we are wrapping which is the
+ // document element, the root frame, the parent view port frame, and we
+ // should get back the new frame and the scrollable view if one was
+ // created.
+
+ // resolve a context for the scrollframe
+ RefPtr<nsStyleContext> styleContext;
+ styleContext = styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::viewportScroll,
+ viewportPseudoStyle);
+
+ // Note that the viewport scrollframe is always built with
+ // overflow:auto style. This forces the scroll frame to create
+ // anonymous content for both scrollbars. This is necessary even
+ // if the HTML or BODY elements are overriding the viewport
+ // scroll style to 'hidden' --- dynamic style changes might put
+ // scrollbars back on the viewport and we don't want to have to
+ // reframe the viewport to create the scrollbar content.
+ newFrame = nullptr;
+ rootPseudoStyle = BeginBuildingScrollFrame( state,
+ aDocElement,
+ styleContext,
+ viewportFrame,
+ rootPseudo,
+ true,
+ newFrame);
+ parentFrame = newFrame;
+ mGfxScrollFrame = newFrame;
+ }
+
+ rootFrame->SetStyleContextWithoutNotification(rootPseudoStyle);
+ rootFrame->Init(aDocElement, parentFrame, nullptr);
+
+ if (isScrollable) {
+ FinishBuildingScrollFrame(parentFrame, rootFrame);
+ }
+
+ if (isPaginated) {
+ // Create the first page
+ // Set the initial child lists
+ nsContainerFrame* canvasFrame;
+ nsContainerFrame* pageFrame =
+ ConstructPageFrame(mPresShell, rootFrame, nullptr, canvasFrame);
+ SetInitialSingleChild(rootFrame, pageFrame);
+
+ // The eventual parent of the document element frame.
+ // XXX should this be set for every new page (in ConstructPageFrame)?
+ mDocElementContainingBlock = canvasFrame;
+ mHasRootAbsPosContainingBlock = true;
+ }
+
+ if (viewportFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
+ SetInitialSingleChild(viewportFrame, newFrame);
+ } else {
+ nsFrameList newFrameList(newFrame, newFrame);
+ viewportFrame->AppendFrames(kPrincipalList, newFrameList);
+ }
+}
+
+void
+nsCSSFrameConstructor::ConstructAnonymousContentForCanvas(nsFrameConstructorState& aState,
+ nsIFrame* aFrame,
+ nsIContent* aDocElement)
+{
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::canvasFrame, "aFrame should be canvas frame!");
+
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
+ GetAnonymousContent(aDocElement, aFrame, anonymousItems);
+ if (anonymousItems.IsEmpty()) {
+ return;
+ }
+
+ FrameConstructionItemList itemsToConstruct;
+ nsContainerFrame* frameAsContainer = do_QueryFrame(aFrame);
+ AddFCItemsForAnonymousContent(aState, frameAsContainer, anonymousItems, itemsToConstruct);
+
+ nsFrameItems frameItems;
+ ConstructFramesFromItemList(aState, itemsToConstruct, frameAsContainer, frameItems);
+ frameAsContainer->AppendFrames(kPrincipalList, frameItems);
+}
+
+nsContainerFrame*
+nsCSSFrameConstructor::ConstructPageFrame(nsIPresShell* aPresShell,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevPageFrame,
+ nsContainerFrame*& aCanvasFrame)
+{
+ nsStyleContext* parentStyleContext = aParentFrame->StyleContext();
+ StyleSetHandle styleSet = aPresShell->StyleSet();
+
+ RefPtr<nsStyleContext> pagePseudoStyle;
+ pagePseudoStyle = styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::page,
+ parentStyleContext);
+
+ nsContainerFrame* pageFrame = NS_NewPageFrame(aPresShell, pagePseudoStyle);
+
+ // Initialize the page frame and force it to have a view. This makes printing of
+ // the pages easier and faster.
+ pageFrame->Init(nullptr, aParentFrame, aPrevPageFrame);
+
+ RefPtr<nsStyleContext> pageContentPseudoStyle;
+ pageContentPseudoStyle =
+ styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::pageContent,
+ pagePseudoStyle);
+
+ nsContainerFrame* pageContentFrame =
+ NS_NewPageContentFrame(aPresShell, pageContentPseudoStyle);
+
+ // Initialize the page content frame and force it to have a view. Also make it the
+ // containing block for fixed elements which are repeated on every page.
+ nsIFrame* prevPageContentFrame = nullptr;
+ if (aPrevPageFrame) {
+ prevPageContentFrame = aPrevPageFrame->PrincipalChildList().FirstChild();
+ NS_ASSERTION(prevPageContentFrame, "missing page content frame");
+ }
+ pageContentFrame->Init(nullptr, pageFrame, prevPageContentFrame);
+ SetInitialSingleChild(pageFrame, pageContentFrame);
+ // Make it an absolute container for fixed-pos elements
+ pageContentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ pageContentFrame->MarkAsAbsoluteContainingBlock();
+
+ RefPtr<nsStyleContext> canvasPseudoStyle;
+ canvasPseudoStyle = styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::canvas,
+ pageContentPseudoStyle);
+
+ aCanvasFrame = NS_NewCanvasFrame(aPresShell, canvasPseudoStyle);
+
+ nsIFrame* prevCanvasFrame = nullptr;
+ if (prevPageContentFrame) {
+ prevCanvasFrame = prevPageContentFrame->PrincipalChildList().FirstChild();
+ NS_ASSERTION(prevCanvasFrame, "missing canvas frame");
+ }
+ aCanvasFrame->Init(nullptr, pageContentFrame, prevCanvasFrame);
+ SetInitialSingleChild(pageContentFrame, aCanvasFrame);
+ return pageFrame;
+}
+
+/* static */
+nsIFrame*
+nsCSSFrameConstructor::CreatePlaceholderFrameFor(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsStyleContext* aParentStyle,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevInFlow,
+ nsFrameState aTypeBit)
+{
+ RefPtr<nsStyleContext> placeholderStyle = aPresShell->StyleSet()->
+ ResolveStyleForOtherNonElement(aParentStyle);
+
+ // The placeholder frame gets a pseudo style context
+ nsPlaceholderFrame* placeholderFrame =
+ (nsPlaceholderFrame*)NS_NewPlaceholderFrame(aPresShell, placeholderStyle,
+ aTypeBit);
+
+ placeholderFrame->Init(aContent, aParentFrame, aPrevInFlow);
+
+ // The placeholder frame has a pointer back to the out-of-flow frame
+ placeholderFrame->SetOutOfFlowFrame(aFrame);
+
+ aFrame->AddStateBits(NS_FRAME_OUT_OF_FLOW);
+
+ // Add mapping from absolutely positioned frame to its placeholder frame
+ aPresShell->FrameManager()->RegisterPlaceholderFrame(placeholderFrame);
+
+ return placeholderFrame;
+}
+
+// Clears any lazy bits set in the range [aStartContent, aEndContent). If
+// aEndContent is null, that means to clear bits in all siblings starting with
+// aStartContent. aStartContent must not be null unless aEndContent is also
+// null. We do this so that when new children are inserted under elements whose
+// frame is a leaf the new children don't cause us to try to construct frames
+// for the existing children again.
+static inline void
+ClearLazyBits(nsIContent* aStartContent, nsIContent* aEndContent)
+{
+ NS_PRECONDITION(aStartContent || !aEndContent,
+ "Must have start child if we have an end child");
+ for (nsIContent* cur = aStartContent; cur != aEndContent;
+ cur = cur->GetNextSibling()) {
+ cur->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ }
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructSelectFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems)
+{
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ // Construct a frame-based listbox or combobox
+ dom::HTMLSelectElement* sel = dom::HTMLSelectElement::FromContent(content);
+ MOZ_ASSERT(sel);
+ if (sel->IsCombobox()) {
+ // Construct a frame-based combo box.
+ // The frame-based combo box is built out of three parts. A display area, a button and
+ // a dropdown list. The display area and button are created through anonymous content.
+ // The drop-down list's frame is created explicitly. The combobox frame shares its content
+ // with the drop-down list.
+ nsFrameState flags = NS_BLOCK_FLOAT_MGR;
+ nsContainerFrame* comboboxFrame =
+ NS_NewComboboxControlFrame(mPresShell, styleContext, flags);
+
+ // Save the history state so we don't restore during construction
+ // since the complete tree is required before we restore.
+ nsILayoutHistoryState *historyState = aState.mFrameState;
+ aState.mFrameState = nullptr;
+ // Initialize the combobox frame
+ InitAndRestoreFrame(aState, content,
+ aState.GetGeometricParent(aStyleDisplay, aParentFrame),
+ comboboxFrame);
+
+ aState.AddChild(comboboxFrame, aFrameItems, content, styleContext,
+ aParentFrame);
+
+ nsIComboboxControlFrame* comboBox = do_QueryFrame(comboboxFrame);
+ NS_ASSERTION(comboBox, "NS_NewComboboxControlFrame returned frame that "
+ "doesn't implement nsIComboboxControlFrame");
+
+ // Resolve pseudo element style for the dropdown list
+ RefPtr<nsStyleContext> listStyle;
+ listStyle = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::dropDownList, styleContext);
+
+ // Create a listbox
+ nsContainerFrame* listFrame = NS_NewListControlFrame(mPresShell, listStyle);
+
+ // Notify the listbox that it is being used as a dropdown list.
+ nsIListControlFrame * listControlFrame = do_QueryFrame(listFrame);
+ if (listControlFrame) {
+ listControlFrame->SetComboboxFrame(comboboxFrame);
+ }
+ // Notify combobox that it should use the listbox as it's popup
+ comboBox->SetDropDown(listFrame);
+
+ NS_ASSERTION(!listFrame->IsAbsPosContainingBlock(),
+ "Ended up with positioned dropdown list somehow.");
+ NS_ASSERTION(!listFrame->IsFloating(),
+ "Ended up with floating dropdown list somehow.");
+
+ // Initialize the scroll frame positioned. Note that it is NOT
+ // initialized as absolutely positioned.
+ nsContainerFrame* scrolledFrame =
+ NS_NewSelectsAreaFrame(mPresShell, styleContext, flags);
+
+ InitializeSelectFrame(aState, listFrame, scrolledFrame, content,
+ comboboxFrame, listStyle, true,
+ aItem.mPendingBinding, aFrameItems);
+
+ NS_ASSERTION(listFrame->GetView(), "ListFrame's view is nullptr");
+
+ // Create display and button frames from the combobox's anonymous content.
+ // The anonymous content is appended to existing anonymous content for this
+ // element (the scrollbars).
+
+ nsFrameItems childItems;
+ CreateAnonymousFrames(aState, content, comboboxFrame,
+ aItem.mPendingBinding, childItems);
+
+ comboboxFrame->SetInitialChildList(kPrincipalList, childItems);
+
+ // Initialize the additional popup child list which contains the
+ // dropdown list frame.
+ nsFrameItems popupItems;
+ popupItems.AddChild(listFrame);
+ comboboxFrame->SetInitialChildList(nsIFrame::kSelectPopupList,
+ popupItems);
+
+ aState.mFrameState = historyState;
+ if (aState.mFrameState) {
+ // Restore frame state for the entire subtree of |comboboxFrame|.
+ RestoreFrameState(comboboxFrame, aState.mFrameState);
+ }
+ return comboboxFrame;
+ }
+
+ // Listbox, not combobox
+ nsContainerFrame* listFrame = NS_NewListControlFrame(mPresShell, styleContext);
+
+ nsContainerFrame* scrolledFrame = NS_NewSelectsAreaFrame(
+ mPresShell, styleContext, NS_BLOCK_FLOAT_MGR);
+
+ // ******* this code stolen from Initialze ScrollFrame ********
+ // please adjust this code to use BuildScrollFrame.
+
+ InitializeSelectFrame(aState, listFrame, scrolledFrame, content,
+ aParentFrame, styleContext, false,
+ aItem.mPendingBinding, aFrameItems);
+
+ return listFrame;
+}
+
+/**
+ * Used to be InitializeScrollFrame but now it's only used for the select tag
+ * But the select tag should really be fixed to use GFX scrollbars that can
+ * be create with BuildScrollFrame.
+ */
+nsresult
+nsCSSFrameConstructor::InitializeSelectFrame(nsFrameConstructorState& aState,
+ nsContainerFrame* scrollFrame,
+ nsContainerFrame* scrolledFrame,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ bool aBuildCombobox,
+ PendingBinding* aPendingBinding,
+ nsFrameItems& aFrameItems)
+{
+ // Initialize it
+ nsContainerFrame* geometricParent =
+ aState.GetGeometricParent(aStyleContext->StyleDisplay(), aParentFrame);
+
+ // We don't call InitAndRestoreFrame for scrollFrame because we can only
+ // restore the frame state after its parts have been created (in particular,
+ // the scrollable view). So we have to split Init and Restore.
+
+ scrollFrame->Init(aContent, geometricParent, nullptr);
+
+ if (!aBuildCombobox) {
+ aState.AddChild(scrollFrame, aFrameItems, aContent,
+ aStyleContext, aParentFrame);
+ }
+
+ if (aBuildCombobox) {
+ nsContainerFrame::CreateViewForFrame(scrollFrame, true);
+ }
+
+ BuildScrollFrame(aState, aContent, aStyleContext, scrolledFrame,
+ geometricParent, scrollFrame);
+
+ if (aState.mFrameState) {
+ // Restore frame state for the scroll frame
+ RestoreFrameStateFor(scrollFrame, aState.mFrameState);
+ }
+
+ // Process children
+ nsFrameItems childItems;
+
+ ProcessChildren(aState, aContent, aStyleContext, scrolledFrame, false,
+ childItems, false, aPendingBinding);
+
+ // Set the scrolled frame's initial child lists
+ scrolledFrame->SetInitialChildList(kPrincipalList, childItems);
+ return NS_OK;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructFieldSetFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems)
+{
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ nsContainerFrame* fieldsetFrame = NS_NewFieldSetFrame(mPresShell, styleContext);
+
+ // Initialize it
+ InitAndRestoreFrame(aState, content,
+ aState.GetGeometricParent(aStyleDisplay, aParentFrame),
+ fieldsetFrame);
+
+ // Resolve style and initialize the frame
+ RefPtr<nsStyleContext> fieldsetContentStyle;
+ fieldsetContentStyle = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::fieldsetContent, styleContext);
+
+ const nsStyleDisplay* fieldsetContentDisplay = fieldsetContentStyle->StyleDisplay();
+ bool isScrollable = fieldsetContentDisplay->IsScrollableOverflow();
+ nsContainerFrame* scrollFrame = nullptr;
+ if (isScrollable) {
+ fieldsetContentStyle =
+ BeginBuildingScrollFrame(aState, content, fieldsetContentStyle,
+ fieldsetFrame, nsCSSAnonBoxes::scrolledContent,
+ false, scrollFrame);
+ }
+
+ nsContainerFrame* absPosContainer = nullptr;
+ if (fieldsetFrame->IsAbsPosContainingBlock()) {
+ absPosContainer = fieldsetFrame;
+ }
+
+ // Create the inner ::-moz-fieldset-content frame.
+ nsContainerFrame* contentFrameTop;
+ nsContainerFrame* contentFrame;
+ auto parent = scrollFrame ? scrollFrame : fieldsetFrame;
+ switch (fieldsetContentDisplay->mDisplay) {
+ case StyleDisplay::Flex:
+ contentFrame = NS_NewFlexContainerFrame(mPresShell, fieldsetContentStyle);
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ break;
+ case StyleDisplay::Grid:
+ contentFrame = NS_NewGridContainerFrame(mPresShell, fieldsetContentStyle);
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ break;
+ default: {
+ MOZ_ASSERT(fieldsetContentDisplay->mDisplay == StyleDisplay::Block,
+ "bug in nsRuleNode::ComputeDisplayData?");
+
+ nsContainerFrame* columnSetFrame = nullptr;
+ RefPtr<nsStyleContext> innerSC = fieldsetContentStyle;
+ const nsStyleColumn* columns = fieldsetContentStyle->StyleColumn();
+ if (columns->mColumnCount != NS_STYLE_COLUMN_COUNT_AUTO ||
+ columns->mColumnWidth.GetUnit() != eStyleUnit_Auto) {
+ columnSetFrame =
+ NS_NewColumnSetFrame(mPresShell, fieldsetContentStyle, nsFrameState(0));
+ InitAndRestoreFrame(aState, content, parent, columnSetFrame);
+ innerSC = mPresShell->StyleSet()->ResolveAnonymousBoxStyle(
+ nsCSSAnonBoxes::columnContent, fieldsetContentStyle);
+ if (absPosContainer) {
+ absPosContainer = columnSetFrame;
+ }
+ }
+ contentFrame = NS_NewBlockFormattingContext(mPresShell, innerSC);
+ if (columnSetFrame) {
+ InitAndRestoreFrame(aState, content, columnSetFrame, contentFrame);
+ SetInitialSingleChild(columnSetFrame, contentFrame);
+ contentFrameTop = columnSetFrame;
+ } else {
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ }
+ break;
+ }
+ }
+
+ aState.AddChild(fieldsetFrame, aFrameItems, content, styleContext, aParentFrame);
+
+ // Process children
+ nsFrameConstructorSaveState absoluteSaveState;
+ nsFrameItems childItems;
+
+ contentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (absPosContainer) {
+ aState.PushAbsoluteContainingBlock(contentFrame, absPosContainer, absoluteSaveState);
+ }
+
+ ProcessChildren(aState, content, styleContext, contentFrame, true,
+ childItems, true, aItem.mPendingBinding);
+
+ nsFrameItems fieldsetKids;
+ fieldsetKids.AddChild(scrollFrame ? scrollFrame : contentFrameTop);
+
+ for (nsFrameList::Enumerator e(childItems); !e.AtEnd(); e.Next()) {
+ nsIFrame* child = e.get();
+ nsContainerFrame* cif = child->GetContentInsertionFrame();
+ if (cif && cif->GetType() == nsGkAtoms::legendFrame) {
+ // We want the legend to be the first frame in the fieldset child list.
+ // That way the EventStateManager will do the right thing when tabbing
+ // from a selection point within the legend (bug 236071), which is
+ // used for implementing legend access keys (bug 81481).
+ // GetAdjustedParentFrame() below depends on this frame order.
+ childItems.RemoveFrame(child);
+ // Make sure to reparent the legend so it has the fieldset as the parent.
+ fieldsetKids.InsertFrame(fieldsetFrame, nullptr, child);
+ if (scrollFrame) {
+ StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary(
+ child, contentFrame);
+ }
+ break;
+ }
+ }
+
+ if (isScrollable) {
+ FinishBuildingScrollFrame(scrollFrame, contentFrameTop);
+ }
+
+ // Set the inner frame's initial child lists
+ contentFrame->SetInitialChildList(kPrincipalList, childItems);
+
+ // Set the outer frame's initial child list
+ fieldsetFrame->SetInitialChildList(kPrincipalList, fieldsetKids);
+
+ fieldsetFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT);
+
+ // Our new frame returned is the outer frame, which is the fieldset frame.
+ return fieldsetFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructDetailsFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems)
+{
+ if (!aStyleDisplay->IsScrollableOverflow()) {
+ return ConstructNonScrollableBlockWithConstructor(aState, aItem, aParentFrame,
+ aStyleDisplay, aFrameItems,
+ NS_NewDetailsFrame);
+ }
+
+ // Build a scroll frame to wrap details frame if necessary.
+ return ConstructScrollableBlockWithConstructor(aState, aItem, aParentFrame,
+ aStyleDisplay, aFrameItems,
+ NS_NewDetailsFrame);
+}
+
+static nsIFrame*
+FindAncestorWithGeneratedContentPseudo(nsIFrame* aFrame)
+{
+ for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
+ NS_ASSERTION(f->IsGeneratedContentFrame(),
+ "should not have exited generated content");
+ nsIAtom* pseudo = f->StyleContext()->GetPseudo();
+ if (pseudo == nsCSSPseudoElements::before ||
+ pseudo == nsCSSPseudoElements::after)
+ return f;
+ }
+ return nullptr;
+}
+
+#define SIMPLE_FCDATA(_func) FCDATA_DECL(0, _func)
+#define FULL_CTOR_FCDATA(_flags, _func) \
+ { _flags | FCDATA_FUNC_IS_FULL_CTOR, { nullptr }, _func, nullptr }
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindTextData(nsIFrame* aParentFrame)
+{
+ if (aParentFrame && IsFrameForSVG(aParentFrame)) {
+ nsIFrame *ancestorFrame =
+ nsSVGUtils::GetFirstNonAAncestorFrame(aParentFrame);
+ if (ancestorFrame) {
+ static const FrameConstructionData sSVGTextData =
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_SVG_TEXT,
+ NS_NewTextFrame);
+ if (ancestorFrame->IsSVGText()) {
+ return &sSVGTextData;
+ }
+ }
+ return nullptr;
+ }
+
+ static const FrameConstructionData sTextData =
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT, NS_NewTextFrame);
+ return &sTextData;
+}
+
+void
+nsCSSFrameConstructor::ConstructTextFrame(const FrameConstructionData* aData,
+ nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ nsFrameItems& aFrameItems)
+{
+ NS_PRECONDITION(aData, "Must have frame construction data");
+
+ nsIFrame* newFrame = (*aData->mFunc.mCreationFunc)(mPresShell, aStyleContext);
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, newFrame);
+
+ // We never need to create a view for a text frame.
+
+ if (newFrame->IsGeneratedContentFrame()) {
+ nsAutoPtr<nsGenConInitializer> initializer;
+ initializer =
+ static_cast<nsGenConInitializer*>(
+ aContent->UnsetProperty(nsGkAtoms::genConInitializerProperty));
+ if (initializer) {
+ if (initializer->mNode->InitTextFrame(initializer->mList,
+ FindAncestorWithGeneratedContentPseudo(newFrame), newFrame)) {
+ (this->*(initializer->mDirtyAll))();
+ }
+ initializer->mNode.forget();
+ }
+ }
+
+ // Add the newly constructed frame to the flow
+ aFrameItems.AddChild(newFrame);
+
+ if (!aState.mCreatingExtraFrames)
+ aContent->SetPrimaryFrame(newFrame);
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDataByInt(int32_t aInt,
+ Element* aElement,
+ nsStyleContext* aStyleContext,
+ const FrameConstructionDataByInt* aDataPtr,
+ uint32_t aDataLength)
+{
+ for (const FrameConstructionDataByInt *curData = aDataPtr,
+ *endData = aDataPtr + aDataLength;
+ curData != endData;
+ ++curData) {
+ if (curData->mInt == aInt) {
+ const FrameConstructionData* data = &curData->mData;
+ if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
+ return data->mFunc.mDataGetter(aElement, aStyleContext);
+ }
+
+ return data;
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDataByTag(nsIAtom* aTag,
+ Element* aElement,
+ nsStyleContext* aStyleContext,
+ const FrameConstructionDataByTag* aDataPtr,
+ uint32_t aDataLength)
+{
+ for (const FrameConstructionDataByTag *curData = aDataPtr,
+ *endData = aDataPtr + aDataLength;
+ curData != endData;
+ ++curData) {
+ if (*curData->mTag == aTag) {
+ const FrameConstructionData* data = &curData->mData;
+ if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
+ return data->mFunc.mDataGetter(aElement, aStyleContext);
+ }
+
+ return data;
+ }
+ }
+
+ return nullptr;
+}
+
+#define SUPPRESS_FCDATA() FCDATA_DECL(FCDATA_SUPPRESS_FRAME, nullptr)
+#define SIMPLE_INT_CREATE(_int, _func) { _int, SIMPLE_FCDATA(_func) }
+#define SIMPLE_INT_CHAIN(_int, _func) \
+ { _int, FCDATA_DECL(FCDATA_FUNC_IS_DATA_GETTER, _func) }
+#define COMPLEX_INT_CREATE(_int, _func) \
+ { _int, FULL_CTOR_FCDATA(0, _func) }
+
+#define SIMPLE_TAG_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, SIMPLE_FCDATA(_func) }
+#define SIMPLE_TAG_CHAIN(_tag, _func) \
+ { &nsGkAtoms::_tag, FCDATA_DECL(FCDATA_FUNC_IS_DATA_GETTER, _func) }
+#define COMPLEX_TAG_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, FULL_CTOR_FCDATA(0, _func) }
+
+static bool
+IsFrameForFieldSet(nsIFrame* aFrame, nsIAtom* aFrameType)
+{
+ nsIAtom* pseudo = aFrame->StyleContext()->GetPseudo();
+ if (pseudo == nsCSSAnonBoxes::fieldsetContent ||
+ pseudo == nsCSSAnonBoxes::scrolledContent ||
+ pseudo == nsCSSAnonBoxes::columnContent) {
+ return IsFrameForFieldSet(aFrame->GetParent(), aFrame->GetParent()->GetType());
+ }
+ return aFrameType == nsGkAtoms::fieldSetFrame;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindHTMLData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsIFrame* aParentFrame,
+ nsStyleContext* aStyleContext)
+{
+ // Ignore the tag if it's not HTML content and if it doesn't extend (via XBL)
+ // a valid HTML namespace. This check must match the one in
+ // ShouldHaveFirstLineStyle.
+ if (aNameSpaceID != kNameSpaceID_XHTML) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(!aParentFrame ||
+ aParentFrame->StyleContext()->GetPseudo() !=
+ nsCSSAnonBoxes::fieldsetContent ||
+ aParentFrame->GetParent()->GetType() == nsGkAtoms::fieldSetFrame,
+ "Unexpected parent for fieldset content anon box");
+ if (aTag == nsGkAtoms::legend &&
+ (!aParentFrame ||
+ !IsFrameForFieldSet(aParentFrame, aParentFrame->GetType()) ||
+ aStyleContext->StyleDisplay()->IsFloatingStyle() ||
+ aStyleContext->StyleDisplay()->IsAbsolutelyPositionedStyle())) {
+ // <legend> is only special inside fieldset, we only check the frame tree
+ // parent because the content tree parent may not be a <fieldset> due to
+ // display:contents, Shadow DOM, or XBL. For floated or absolutely
+ // positioned legends we want to construct by display type and
+ // not do special legend stuff.
+ return nullptr;
+ }
+
+ if (aTag == nsGkAtoms::details && !HTMLDetailsElement::IsDetailsEnabled()) {
+ return nullptr;
+ }
+
+ static const FrameConstructionDataByTag sHTMLData[] = {
+ SIMPLE_TAG_CHAIN(img, nsCSSFrameConstructor::FindImgData),
+ SIMPLE_TAG_CHAIN(mozgeneratedcontentimage,
+ nsCSSFrameConstructor::FindImgData),
+ { &nsGkAtoms::br,
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_LINE_BREAK,
+ NS_NewBRFrame) },
+ SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame),
+ SIMPLE_TAG_CHAIN(input, nsCSSFrameConstructor::FindInputData),
+ SIMPLE_TAG_CREATE(textarea, NS_NewTextControlFrame),
+ COMPLEX_TAG_CREATE(select, &nsCSSFrameConstructor::ConstructSelectFrame),
+ SIMPLE_TAG_CHAIN(object, nsCSSFrameConstructor::FindObjectData),
+ SIMPLE_TAG_CHAIN(applet, nsCSSFrameConstructor::FindObjectData),
+ SIMPLE_TAG_CHAIN(embed, nsCSSFrameConstructor::FindObjectData),
+ COMPLEX_TAG_CREATE(fieldset,
+ &nsCSSFrameConstructor::ConstructFieldSetFrame),
+ { &nsGkAtoms::legend,
+ FCDATA_DECL(FCDATA_ALLOW_BLOCK_STYLES | FCDATA_MAY_NEED_SCROLLFRAME,
+ NS_NewLegendFrame) },
+ SIMPLE_TAG_CREATE(frameset, NS_NewHTMLFramesetFrame),
+ SIMPLE_TAG_CREATE(iframe, NS_NewSubDocumentFrame),
+ { &nsGkAtoms::button,
+ FCDATA_WITH_WRAPPING_BLOCK(FCDATA_ALLOW_BLOCK_STYLES |
+ FCDATA_ALLOW_GRID_FLEX_COLUMNSET,
+ NS_NewHTMLButtonControlFrame,
+ nsCSSAnonBoxes::buttonContent) },
+ SIMPLE_TAG_CHAIN(canvas, nsCSSFrameConstructor::FindCanvasData),
+ SIMPLE_TAG_CREATE(video, NS_NewHTMLVideoFrame),
+ SIMPLE_TAG_CREATE(audio, NS_NewHTMLVideoFrame),
+ SIMPLE_TAG_CREATE(progress, NS_NewProgressFrame),
+ SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame),
+ COMPLEX_TAG_CREATE(details, &nsCSSFrameConstructor::ConstructDetailsFrame)
+ };
+
+ return FindDataByTag(aTag, aElement, aStyleContext, sHTMLData,
+ ArrayLength(sHTMLData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindImgData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ if (!nsImageFrame::ShouldCreateImageFrameFor(aElement, aStyleContext)) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sImgData = SIMPLE_FCDATA(NS_NewImageFrame);
+ return &sImgData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindImgControlData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ if (!nsImageFrame::ShouldCreateImageFrameFor(aElement, aStyleContext)) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sImgControlData =
+ SIMPLE_FCDATA(NS_NewImageControlFrame);
+ return &sImgControlData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindInputData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ static const FrameConstructionDataByInt sInputData[] = {
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_CHECKBOX, NS_NewGfxCheckboxControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_RADIO, NS_NewGfxRadioControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_FILE, NS_NewFileControlFrame),
+ SIMPLE_INT_CHAIN(NS_FORM_INPUT_IMAGE,
+ nsCSSFrameConstructor::FindImgControlData),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_EMAIL, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_SEARCH, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_TEXT, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_TEL, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_URL, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_RANGE, NS_NewRangeFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_PASSWORD, NS_NewTextControlFrame),
+ { NS_FORM_INPUT_COLOR,
+ FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewColorControlFrame,
+ nsCSSAnonBoxes::buttonContent) },
+ // TODO: this is temporary until a frame is written: bug 635240.
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
+ // TODO: this is temporary until a frame is written: bug 888320.
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ // On Android/B2G, date/time input appears as a normal text box.
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
+#else
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
+#endif
+ // TODO: this is temporary until a frame is written: bug 888320
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
+ // TODO: this is temporary until a frame is written: bug 888320
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_WEEK, NS_NewTextControlFrame),
+ // TODO: this is temporary until a frame is written: bug 888320
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_DATETIME_LOCAL, NS_NewTextControlFrame),
+ { NS_FORM_INPUT_SUBMIT,
+ FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
+ nsCSSAnonBoxes::buttonContent) },
+ { NS_FORM_INPUT_RESET,
+ FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
+ nsCSSAnonBoxes::buttonContent) },
+ { NS_FORM_INPUT_BUTTON,
+ FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
+ nsCSSAnonBoxes::buttonContent) }
+ // Keeping hidden inputs out of here on purpose for so they get frames by
+ // display (in practice, none).
+ };
+
+ nsCOMPtr<nsIFormControl> control = do_QueryInterface(aElement);
+ NS_ASSERTION(control, "input doesn't implement nsIFormControl?");
+
+ return FindDataByInt(control->GetType(), aElement, aStyleContext,
+ sInputData, ArrayLength(sInputData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindObjectData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ // GetDisplayedType isn't necessarily nsIObjectLoadingContent::TYPE_NULL for
+ // cases when the object is broken/suppressed/etc (e.g. a broken image), but
+ // we want to treat those cases as TYPE_NULL
+ uint32_t type;
+ if (aElement->State().HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |
+ NS_EVENT_STATE_USERDISABLED |
+ NS_EVENT_STATE_SUPPRESSED)) {
+ type = nsIObjectLoadingContent::TYPE_NULL;
+ } else {
+ nsCOMPtr<nsIObjectLoadingContent> objContent(do_QueryInterface(aElement));
+ NS_ASSERTION(objContent,
+ "applet, embed and object must implement "
+ "nsIObjectLoadingContent!");
+
+ objContent->GetDisplayedType(&type);
+ }
+
+ static const FrameConstructionDataByInt sObjectData[] = {
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_LOADING,
+ NS_NewEmptyFrame),
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_PLUGIN,
+ NS_NewObjectFrame),
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_IMAGE,
+ NS_NewImageFrame),
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_DOCUMENT,
+ NS_NewSubDocumentFrame)
+ // Nothing for TYPE_NULL so we'll construct frames by display there
+ };
+
+ return FindDataByInt((int32_t)type, aElement, aStyleContext,
+ sObjectData, ArrayLength(sObjectData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindCanvasData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ // We want to check whether script is enabled on the document that
+ // could be painting to the canvas. That's the owner document of
+ // the canvas, except when the owner document is a static document,
+ // in which case it's the original document it was cloned from.
+ nsIDocument* doc = aElement->OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ doc = doc->GetOriginalDocument();
+ }
+ if (!doc->IsScriptEnabled()) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sCanvasData =
+ FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewHTMLCanvasFrame,
+ nsCSSAnonBoxes::htmlCanvasContent);
+ return &sCanvasData;
+}
+
+void
+nsCSSFrameConstructor::ConstructFrameFromItemInternal(FrameConstructionItem& aItem,
+ nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems)
+{
+ const FrameConstructionData* data = aItem.mFCData;
+ NS_ASSERTION(data, "Must have frame construction data");
+
+ uint32_t bits = data->mBits;
+
+ NS_ASSERTION(!(bits & FCDATA_FUNC_IS_DATA_GETTER),
+ "Should have dealt with this inside the data finder");
+
+ // Some sets of bits are not compatible with each other
+#define CHECK_ONLY_ONE_BIT(_bit1, _bit2) \
+ NS_ASSERTION(!(bits & _bit1) || !(bits & _bit2), \
+ "Only one of these bits should be set")
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_FORCE_NULL_ABSPOS_CONTAINER);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_WRAP_KIDS_IN_BLOCKS);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_MAY_NEED_SCROLLFRAME);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_IS_POPUP);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_SKIP_ABSPOS_PUSH);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
+ FCDATA_DISALLOW_GENERATED_CONTENT);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_ALLOW_BLOCK_STYLES);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
+ FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
+ CHECK_ONLY_ONE_BIT(FCDATA_WRAP_KIDS_IN_BLOCKS,
+ FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
+#undef CHECK_ONLY_ONE_BIT
+ NS_ASSERTION(!(bits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) ||
+ ((bits & FCDATA_FUNC_IS_FULL_CTOR) &&
+ data->mFullConstructor ==
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock),
+ "Unexpected FCDATA_FORCED_NON_SCROLLABLE_BLOCK flag");
+
+ // Don't create a subdocument frame for iframes if we're creating extra frames
+ if (aState.mCreatingExtraFrames &&
+ aItem.mContent->IsHTMLElement(nsGkAtoms::iframe))
+ {
+ return;
+ }
+
+ nsIContent* const content = aItem.mContent;
+ nsIContent* parent = content->GetParent();
+
+ // Push display:contents ancestors.
+ AutoDisplayContentsAncestorPusher adcp(aState.mTreeMatchContext,
+ aState.mPresContext, parent);
+
+ // Get the parent of the content and check if it is a XBL children element.
+ // Push the children element as an ancestor here because it does
+ // not have a frame and would not otherwise be pushed as an ancestor. It is
+ // necessary to do so in order to correctly handle style resolution on
+ // descendants. (If !adcp.IsEmpty() then it was already pushed by
+ // AutoDisplayContentsAncestorPusher above.)
+ TreeMatchContext::AutoAncestorPusher
+ insertionPointPusher(aState.mTreeMatchContext);
+ if (adcp.IsEmpty() && parent && nsContentUtils::IsContentInsertionPoint(parent)) {
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ insertionPointPusher.PushAncestorAndStyleScope(parent);
+ } else {
+ insertionPointPusher.PushStyleScope(parent);
+ }
+ }
+
+ // Push the content as a style ancestor now, so we don't have to do
+ // it in our various full-constructor functions. In particular,
+ // since a number of full-constructor functions don't actually call
+ // ProcessChildren in some cases (e.g. for CSS anonymous table boxes
+ // or for situations where only anonymouse children are having
+ // frames constructed), this is the best place to bottleneck the
+ // pushing of the content instead of having to do it in multiple
+ // places.
+ TreeMatchContext::AutoAncestorPusher
+ ancestorPusher(aState.mTreeMatchContext);
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(content);
+ } else {
+ ancestorPusher.PushStyleScope(content);
+ }
+
+ nsIFrame* newFrame;
+ nsIFrame* primaryFrame;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+ const nsStyleDisplay* display = styleContext->StyleDisplay();
+ if (bits & FCDATA_FUNC_IS_FULL_CTOR) {
+ newFrame =
+ (this->*(data->mFullConstructor))(aState, aItem, aParentFrame,
+ display, aFrameItems);
+ MOZ_ASSERT(newFrame, "Full constructor failed");
+ primaryFrame = newFrame;
+ } else {
+ newFrame =
+ (*data->mFunc.mCreationFunc)(mPresShell, styleContext);
+
+ bool allowOutOfFlow = !(bits & FCDATA_DISALLOW_OUT_OF_FLOW);
+ bool isPopup = aItem.mIsPopup;
+ NS_ASSERTION(!isPopup ||
+ (aState.mPopupItems.containingBlock &&
+ aState.mPopupItems.containingBlock->GetType() ==
+ nsGkAtoms::popupSetFrame),
+ "Should have a containing block here!");
+
+ nsContainerFrame* geometricParent =
+ isPopup ? aState.mPopupItems.containingBlock :
+ (allowOutOfFlow ? aState.GetGeometricParent(display, aParentFrame)
+ : aParentFrame);
+
+ // Must init frameToAddToList to null, since it's inout
+ nsIFrame* frameToAddToList = nullptr;
+ if ((bits & FCDATA_MAY_NEED_SCROLLFRAME) &&
+ display->IsScrollableOverflow()) {
+ nsContainerFrame* scrollframe = nullptr;
+ BuildScrollFrame(aState, content, styleContext, newFrame,
+ geometricParent, scrollframe);
+ frameToAddToList = scrollframe;
+ } else {
+ InitAndRestoreFrame(aState, content, geometricParent, newFrame);
+ // See whether we need to create a view
+ nsContainerFrame::CreateViewForFrame(newFrame, false);
+ frameToAddToList = newFrame;
+ }
+
+ // Use frameToAddToList as the primary frame. In the non-scrollframe case
+ // they're equal, but in the scrollframe case newFrame is the scrolled
+ // frame, while frameToAddToList is the scrollframe (and should be the
+ // primary frame).
+ primaryFrame = frameToAddToList;
+
+ // If we need to create a block formatting context to wrap our
+ // kids, do it now.
+ const nsStyleDisplay* maybeAbsoluteContainingBlockDisplay = display;
+ nsIFrame* maybeAbsoluteContainingBlockStyleFrame = primaryFrame;
+ nsIFrame* maybeAbsoluteContainingBlock = newFrame;
+ nsIFrame* possiblyLeafFrame = newFrame;
+ if (bits & FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS) {
+ RefPtr<nsStyleContext> outerSC =
+ mPresShell->StyleSet()->ResolveAnonymousBoxStyle(*data->mAnonBoxPseudo,
+ styleContext);
+#ifdef DEBUG
+ nsContainerFrame* containerFrame = do_QueryFrame(newFrame);
+ MOZ_ASSERT(containerFrame);
+#endif
+ nsContainerFrame* container = static_cast<nsContainerFrame*>(newFrame);
+ nsContainerFrame* outerFrame;
+ nsContainerFrame* innerFrame;
+ if (bits & FCDATA_ALLOW_GRID_FLEX_COLUMNSET) {
+ switch (display->mDisplay) {
+ case StyleDisplay::Flex:
+ case StyleDisplay::InlineFlex:
+ outerFrame = NS_NewFlexContainerFrame(mPresShell, outerSC);
+ InitAndRestoreFrame(aState, content, container, outerFrame);
+ innerFrame = outerFrame;
+ break;
+ case StyleDisplay::Grid:
+ case StyleDisplay::InlineGrid:
+ outerFrame = NS_NewGridContainerFrame(mPresShell, outerSC);
+ InitAndRestoreFrame(aState, content, container, outerFrame);
+ innerFrame = outerFrame;
+ break;
+ default: {
+ nsContainerFrame* columnSetFrame = nullptr;
+ RefPtr<nsStyleContext> innerSC = outerSC;
+ const nsStyleColumn* columns = outerSC->StyleColumn();
+ if (columns->mColumnCount != NS_STYLE_COLUMN_COUNT_AUTO ||
+ columns->mColumnWidth.GetUnit() != eStyleUnit_Auto) {
+ columnSetFrame =
+ NS_NewColumnSetFrame(mPresShell, outerSC, nsFrameState(0));
+ InitAndRestoreFrame(aState, content, container, columnSetFrame);
+ innerSC = mPresShell->StyleSet()->ResolveAnonymousBoxStyle(
+ nsCSSAnonBoxes::columnContent, outerSC);
+ }
+ innerFrame = NS_NewBlockFormattingContext(mPresShell, innerSC);
+ if (columnSetFrame) {
+ InitAndRestoreFrame(aState, content, columnSetFrame, innerFrame);
+ SetInitialSingleChild(columnSetFrame, innerFrame);
+ outerFrame = columnSetFrame;
+ } else {
+ InitAndRestoreFrame(aState, content, container, innerFrame);
+ outerFrame = innerFrame;
+ }
+ break;
+ }
+ }
+ } else {
+ innerFrame = NS_NewBlockFormattingContext(mPresShell, outerSC);
+ InitAndRestoreFrame(aState, content, container, innerFrame);
+ outerFrame = innerFrame;
+ }
+
+ SetInitialSingleChild(container, outerFrame);
+
+ // Now figure out whether newFrame or outerFrame should be the
+ // absolute container.
+ auto outerDisplay = outerSC->StyleDisplay();
+ if (outerDisplay->IsAbsPosContainingBlock(outerFrame)) {
+ maybeAbsoluteContainingBlockDisplay = outerDisplay;
+ maybeAbsoluteContainingBlock = outerFrame;
+ maybeAbsoluteContainingBlockStyleFrame = outerFrame;
+ innerFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ }
+
+ // Our kids should go into the innerFrame.
+ newFrame = innerFrame;
+ }
+
+ aState.AddChild(frameToAddToList, aFrameItems, content, styleContext,
+ aParentFrame, allowOutOfFlow, allowOutOfFlow, isPopup);
+
+ nsContainerFrame* newFrameAsContainer = do_QueryFrame(newFrame);
+ if (newFrameAsContainer) {
+#ifdef MOZ_XUL
+ // Icky XUL stuff, sadly
+
+ if (aItem.mIsRootPopupgroup) {
+ NS_ASSERTION(nsIRootBox::GetRootBox(mPresShell) &&
+ nsIRootBox::GetRootBox(mPresShell)->GetPopupSetFrame() ==
+ newFrame,
+ "Unexpected PopupSetFrame");
+ aState.mPopupItems.containingBlock = newFrameAsContainer;
+ aState.mHavePendingPopupgroup = false;
+ }
+#endif /* MOZ_XUL */
+
+ // Process the child content if requested
+ nsFrameItems childItems;
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ if (bits & FCDATA_FORCE_NULL_ABSPOS_CONTAINER) {
+ aState.PushAbsoluteContainingBlock(nullptr, nullptr, absoluteSaveState);
+ } else if (!(bits & FCDATA_SKIP_ABSPOS_PUSH)) {
+ maybeAbsoluteContainingBlock->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ // This check is identical to nsStyleDisplay::IsAbsPosContainingBlock
+ // except without the assertion that the style display and frame match.
+ // When constructing scroll frames we intentionally use the style
+ // display for the outer, but make the inner the containing block.
+ if ((maybeAbsoluteContainingBlockDisplay->IsAbsolutelyPositionedStyle() ||
+ maybeAbsoluteContainingBlockDisplay->IsRelativelyPositionedStyle() ||
+ maybeAbsoluteContainingBlockDisplay->IsFixedPosContainingBlock(
+ maybeAbsoluteContainingBlockStyleFrame)) &&
+ !maybeAbsoluteContainingBlockStyleFrame->IsSVGText()) {
+ nsContainerFrame* cf = static_cast<nsContainerFrame*>(
+ maybeAbsoluteContainingBlock);
+ aState.PushAbsoluteContainingBlock(cf, cf, absoluteSaveState);
+ }
+ }
+
+ if (!aItem.mAnonChildren.IsEmpty()) {
+ NS_ASSERTION(!(bits & FCDATA_USE_CHILD_ITEMS),
+ "We should not have both anonymous and non-anonymous "
+ "children in a given FrameConstructorItem");
+ AddFCItemsForAnonymousContent(aState, newFrameAsContainer, aItem.mAnonChildren,
+ aItem.mChildItems);
+ bits |= FCDATA_USE_CHILD_ITEMS;
+ }
+
+ if (bits & FCDATA_USE_CHILD_ITEMS) {
+ nsFrameConstructorSaveState floatSaveState;
+
+ if (ShouldSuppressFloatingOfDescendants(newFrame)) {
+ aState.PushFloatContainingBlock(nullptr, floatSaveState);
+ } else if (newFrame->IsFloatContainingBlock()) {
+ aState.PushFloatContainingBlock(newFrameAsContainer, floatSaveState);
+ }
+ ConstructFramesFromItemList(aState, aItem.mChildItems, newFrameAsContainer,
+ childItems);
+ } else {
+ // Process the child frames.
+ ProcessChildren(aState, content, styleContext, newFrameAsContainer,
+ !(bits & FCDATA_DISALLOW_GENERATED_CONTENT),
+ childItems,
+ (bits & FCDATA_ALLOW_BLOCK_STYLES) != 0,
+ aItem.mPendingBinding, possiblyLeafFrame);
+ }
+
+ if (bits & FCDATA_WRAP_KIDS_IN_BLOCKS) {
+ nsFrameItems newItems;
+ nsFrameItems currentBlockItems;
+ nsIFrame* f;
+ while ((f = childItems.FirstChild()) != nullptr) {
+ bool wrapFrame = IsInlineFrame(f) || IsFramePartOfIBSplit(f);
+ if (!wrapFrame) {
+ FlushAccumulatedBlock(aState, content, newFrameAsContainer,
+ currentBlockItems, newItems);
+ }
+
+ childItems.RemoveFrame(f);
+ if (wrapFrame) {
+ currentBlockItems.AddChild(f);
+ } else {
+ newItems.AddChild(f);
+ }
+ }
+ FlushAccumulatedBlock(aState, content, newFrameAsContainer,
+ currentBlockItems, newItems);
+
+ if (childItems.NotEmpty()) {
+ // an error must have occurred, delete unprocessed frames
+ childItems.DestroyFrames();
+ }
+
+ childItems = newItems;
+ }
+
+ // Set the frame's initial child list
+ // Note that MathML depends on this being called even if
+ // childItems is empty!
+ newFrameAsContainer->SetInitialChildList(kPrincipalList, childItems);
+ }
+ }
+
+#ifdef MOZ_XUL
+ // More icky XUL stuff
+ if (aItem.mNameSpaceID == kNameSpaceID_XUL &&
+ (aItem.mTag == nsGkAtoms::treechildren || // trees always need titletips
+ content->HasAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext) ||
+ content->HasAttr(kNameSpaceID_None, nsGkAtoms::tooltip))) {
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresShell);
+ if (rootBox) {
+ rootBox->AddTooltipSupport(content);
+ }
+ }
+#endif
+
+ NS_ASSERTION(newFrame->IsFrameOfType(nsIFrame::eLineParticipant) ==
+ ((bits & FCDATA_IS_LINE_PARTICIPANT) != 0),
+ "Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits");
+
+ if (aItem.mIsAnonymousContentCreatorContent) {
+ primaryFrame->AddStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT);
+ }
+
+ // Even if mCreatingExtraFrames is set, we may need to SetPrimaryFrame for
+ // generated content that doesn't have one yet. Note that we have to examine
+ // the frame bit, because by this point mIsGeneratedContent has been cleared
+ // on aItem.
+ if ((!aState.mCreatingExtraFrames ||
+ (primaryFrame->HasAnyStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT |
+ NS_FRAME_GENERATED_CONTENT) &&
+ !aItem.mContent->GetPrimaryFrame())) &&
+ !(bits & FCDATA_SKIP_FRAMESET)) {
+ aItem.mContent->SetPrimaryFrame(primaryFrame);
+ ActiveLayerTracker::TransferActivityToFrame(aItem.mContent, primaryFrame);
+ }
+}
+
+// after the node has been constructed and initialized create any
+// anonymous content a node needs.
+nsresult
+nsCSSFrameConstructor::CreateAnonymousFrames(nsFrameConstructorState& aState,
+ nsIContent* aParent,
+ nsContainerFrame* aParentFrame,
+ PendingBinding* aPendingBinding,
+ nsFrameItems& aChildItems)
+{
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> newAnonymousItems;
+ nsresult rv = GetAnonymousContent(aParent, aParentFrame, newAnonymousItems);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count = newAnonymousItems.Length();
+ if (count == 0) {
+ return NS_OK;
+ }
+
+ nsFrameConstructorState::PendingBindingAutoPusher pusher(aState,
+ aPendingBinding);
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(aState.mTreeMatchContext);
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(aParent->AsElement());
+ } else {
+ ancestorPusher.PushStyleScope(aParent->AsElement());
+ }
+
+ nsIAnonymousContentCreator* creator = do_QueryFrame(aParentFrame);
+ NS_ASSERTION(creator,
+ "How can that happen if we have nodes to construct frames for?");
+
+ InsertionPoint insertion(aParentFrame, aParent);
+ for (uint32_t i=0; i < count; i++) {
+ nsIContent* content = newAnonymousItems[i].mContent;
+ NS_ASSERTION(content, "null anonymous content?");
+ NS_ASSERTION(!newAnonymousItems[i].mStyleContext, "Unexpected style context");
+ NS_ASSERTION(newAnonymousItems[i].mChildren.IsEmpty(),
+ "This method is not currently used with frames that implement "
+ "nsIAnonymousContentCreator::CreateAnonymousContent to "
+ "output a list where the items have their own children");
+
+ nsIFrame* newFrame = creator->CreateFrameFor(content);
+ if (newFrame) {
+ NS_ASSERTION(content->GetPrimaryFrame(),
+ "Content must have a primary frame now");
+ newFrame->AddStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT);
+ aChildItems.AddChild(newFrame);
+ } else {
+ FrameConstructionItemList items;
+ {
+ // Skip parent display based style-fixup during our
+ // AddFrameConstructionItems() call:
+ TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
+ parentDisplayBasedStyleFixupSkipper(aState.mTreeMatchContext);
+
+ AddFrameConstructionItems(aState, content, true, insertion, items);
+ }
+ ConstructFramesFromItemList(aState, items, aParentFrame, aChildItems);
+ }
+ }
+
+ return NS_OK;
+}
+
+static void
+SetFlagsOnSubtree(nsIContent *aNode, uintptr_t aFlagsToSet)
+{
+#ifdef DEBUG
+ // Make sure that the node passed to us doesn't have any XBL children
+ {
+ FlattenedChildIterator iter(aNode);
+ NS_ASSERTION(!iter.XBLInvolved() || !iter.GetNextChild(),
+ "The node should not have any XBL children");
+ }
+#endif
+
+ // Set the flag on the node itself
+ aNode->SetFlags(aFlagsToSet);
+
+ // Set the flag on all of its children recursively
+ uint32_t count;
+ nsIContent * const *children = aNode->GetChildArray(&count);
+
+ for (uint32_t index = 0; index < count; ++index) {
+ SetFlagsOnSubtree(children[index], aFlagsToSet);
+ }
+}
+
+/**
+ * This function takes a tree of nsIAnonymousContentCreator::ContentInfo
+ * objects where the nsIContent nodes have just been created, and appends the
+ * nsIContent children in the tree to their parent. The leaf nsIContent objects
+ * are appended first to minimize the number of notifications that are sent
+ * out (i.e. by appending as many descendants as posible while their parent is
+ * not yet in the document tree).
+ *
+ * This function is used simply as a convenience so that implementations of
+ * nsIAnonymousContentCreator::CreateAnonymousContent don't all have to have
+ * their own code to connect the elements that they create.
+ */
+static void
+ConnectAnonymousTreeDescendants(nsIContent* aParent,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aContent)
+{
+ uint32_t count = aContent.Length();
+ for (uint32_t i=0; i < count; i++) {
+ nsIContent* content = aContent[i].mContent;
+ NS_ASSERTION(content, "null anonymous content?");
+
+ ConnectAnonymousTreeDescendants(content, aContent[i].mChildren);
+
+ aParent->AppendChildTo(content, false);
+ }
+}
+
+nsresult
+nsCSSFrameConstructor::GetAnonymousContent(nsIContent* aParent,
+ nsIFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aContent)
+{
+ nsIAnonymousContentCreator* creator = do_QueryFrame(aParentFrame);
+ if (!creator)
+ return NS_OK;
+
+ nsresult rv = creator->CreateAnonymousContent(aContent);
+ if (NS_FAILED(rv)) {
+ // CreateAnonymousContent failed, e.g. because the page has a <use> loop.
+ return rv;
+ }
+
+ uint32_t count = aContent.Length();
+ for (uint32_t i=0; i < count; i++) {
+ // get our child's content and set its parent to our content
+ nsIContent* content = aContent[i].mContent;
+ NS_ASSERTION(content, "null anonymous content?");
+
+ // least-surprise CSS binding until we do the SVG specified
+ // cascading rules for <svg:use> - bug 265894
+ if (aParentFrame->GetType() == nsGkAtoms::svgUseFrame) {
+ content->SetFlags(NODE_IS_ANONYMOUS_ROOT);
+ } else {
+ content->SetIsNativeAnonymousRoot();
+ }
+
+ ConnectAnonymousTreeDescendants(content, aContent[i].mChildren);
+
+ bool anonContentIsEditable = content->HasFlag(NODE_IS_EDITABLE);
+
+ // If the parent is in a shadow tree, make sure we don't
+ // bind with a document because shadow roots and its descendants
+ // are not in document.
+ nsIDocument* bindDocument =
+ aParent->HasFlag(NODE_IS_IN_SHADOW_TREE) ? nullptr : mDocument;
+ rv = content->BindToTree(bindDocument, aParent, aParent, true);
+ // If the anonymous content creator requested that the content should be
+ // editable, honor its request.
+ // We need to set the flag on the whole subtree, because existing
+ // children's flags have already been set as part of the BindToTree operation.
+ if (anonContentIsEditable) {
+ NS_ASSERTION(aParentFrame->GetType() == nsGkAtoms::textInputFrame,
+ "We only expect this for anonymous content under a text control frame");
+ SetFlagsOnSubtree(content, NODE_IS_EDITABLE);
+ }
+ if (NS_FAILED(rv)) {
+ content->UnbindFromTree();
+ return rv;
+ }
+ }
+
+ if (ServoStyleSet* styleSet = mPresShell->StyleSet()->GetAsServo()) {
+ // Eagerly compute styles for the anonymous content tree, but only do so
+ // if the content doesn't have an explicit style context (if it does, we
+ // don't need the normal computed values).
+ for (auto& info : aContent) {
+ if (!info.mStyleContext) {
+ styleSet->StyleNewSubtree(info.mContent);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static
+bool IsXULDisplayType(const nsStyleDisplay* aDisplay)
+{
+ return (aDisplay->mDisplay == StyleDisplay::InlineBox ||
+#ifdef MOZ_XUL
+ aDisplay->mDisplay == StyleDisplay::InlineXulGrid ||
+ aDisplay->mDisplay == StyleDisplay::InlineStack ||
+#endif
+ aDisplay->mDisplay == StyleDisplay::Box
+#ifdef MOZ_XUL
+ || aDisplay->mDisplay == StyleDisplay::XulGrid ||
+ aDisplay->mDisplay == StyleDisplay::Stack ||
+ aDisplay->mDisplay == StyleDisplay::XulGridGroup ||
+ aDisplay->mDisplay == StyleDisplay::XulGridLine ||
+ aDisplay->mDisplay == StyleDisplay::Deck ||
+ aDisplay->mDisplay == StyleDisplay::Popup ||
+ aDisplay->mDisplay == StyleDisplay::Groupbox
+#endif
+ );
+}
+
+
+// XUL frames are not allowed to be out of flow.
+#define SIMPLE_XUL_FCDATA(_func) \
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH, \
+ _func)
+#define SCROLLABLE_XUL_FCDATA(_func) \
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH | \
+ FCDATA_MAY_NEED_SCROLLFRAME, _func)
+// .. but we allow some XUL frames to be _containers_ for out-of-flow content
+// (This is the same as SCROLLABLE_XUL_FCDATA, but w/o FCDATA_SKIP_ABSPOS_PUSH)
+#define SCROLLABLE_ABSPOS_CONTAINER_XUL_FCDATA(_func) \
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_MAY_NEED_SCROLLFRAME, _func)
+
+#define SIMPLE_XUL_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, SIMPLE_XUL_FCDATA(_func) }
+#define SCROLLABLE_XUL_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, SCROLLABLE_XUL_FCDATA(_func) }
+#define SIMPLE_XUL_DISPLAY_CREATE(_display, _func) \
+ FCDATA_FOR_DISPLAY(_display, SIMPLE_XUL_FCDATA(_func))
+#define SCROLLABLE_XUL_DISPLAY_CREATE(_display, _func) \
+ FCDATA_FOR_DISPLAY(_display, SCROLLABLE_XUL_FCDATA(_func))
+#define SCROLLABLE_ABSPOS_CONTAINER_XUL_DISPLAY_CREATE(_display, _func) \
+ FCDATA_FOR_DISPLAY(_display, SCROLLABLE_ABSPOS_CONTAINER_XUL_FCDATA(_func))
+
+static
+nsIFrame* NS_NewGridBoxFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aStyleContext)
+{
+ nsCOMPtr<nsBoxLayout> layout;
+ NS_NewGridLayout2(aPresShell, getter_AddRefs(layout));
+ return NS_NewBoxFrame(aPresShell, aStyleContext, false, layout);
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULTagData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsStyleContext* aStyleContext)
+{
+ if (aNameSpaceID != kNameSpaceID_XUL) {
+ return nullptr;
+ }
+
+ static const FrameConstructionDataByTag sXULTagData[] = {
+#ifdef MOZ_XUL
+ SCROLLABLE_XUL_CREATE(button, NS_NewButtonBoxFrame),
+ SCROLLABLE_XUL_CREATE(checkbox, NS_NewButtonBoxFrame),
+ SCROLLABLE_XUL_CREATE(radio, NS_NewButtonBoxFrame),
+ SCROLLABLE_XUL_CREATE(autorepeatbutton, NS_NewAutoRepeatBoxFrame),
+ SCROLLABLE_XUL_CREATE(titlebar, NS_NewTitleBarFrame),
+ SCROLLABLE_XUL_CREATE(resizer, NS_NewResizerFrame),
+ SIMPLE_XUL_CREATE(image, NS_NewImageBoxFrame),
+ SIMPLE_XUL_CREATE(spring, NS_NewLeafBoxFrame),
+ SIMPLE_XUL_CREATE(spacer, NS_NewLeafBoxFrame),
+ SIMPLE_XUL_CREATE(treechildren, NS_NewTreeBodyFrame),
+ SIMPLE_XUL_CREATE(treecol, NS_NewTreeColFrame),
+ SIMPLE_XUL_CREATE(text, NS_NewTextBoxFrame),
+ SIMPLE_TAG_CHAIN(label, nsCSSFrameConstructor::FindXULLabelData),
+ SIMPLE_TAG_CHAIN(description, nsCSSFrameConstructor::FindXULDescriptionData),
+ SIMPLE_XUL_CREATE(menu, NS_NewMenuFrame),
+ SIMPLE_XUL_CREATE(menubutton, NS_NewMenuFrame),
+ SIMPLE_XUL_CREATE(menuitem, NS_NewMenuItemFrame),
+#ifdef XP_MACOSX
+ SIMPLE_TAG_CHAIN(menubar, nsCSSFrameConstructor::FindXULMenubarData),
+#else
+ SIMPLE_XUL_CREATE(menubar, NS_NewMenuBarFrame),
+#endif /* XP_MACOSX */
+ SIMPLE_TAG_CHAIN(popupgroup, nsCSSFrameConstructor::FindPopupGroupData),
+ SIMPLE_XUL_CREATE(iframe, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(editor, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(browser, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(progressmeter, NS_NewProgressMeterFrame),
+ SIMPLE_XUL_CREATE(splitter, NS_NewSplitterFrame),
+ SIMPLE_TAG_CHAIN(listboxbody,
+ nsCSSFrameConstructor::FindXULListBoxBodyData),
+ SIMPLE_TAG_CHAIN(listitem, nsCSSFrameConstructor::FindXULListItemData),
+#endif /* MOZ_XUL */
+ SIMPLE_XUL_CREATE(slider, NS_NewSliderFrame),
+ SIMPLE_XUL_CREATE(scrollbar, NS_NewScrollbarFrame),
+ SIMPLE_XUL_CREATE(scrollbarbutton, NS_NewScrollbarButtonFrame)
+};
+
+ return FindDataByTag(aTag, aElement, aStyleContext, sXULTagData,
+ ArrayLength(sXULTagData));
+}
+
+#ifdef MOZ_XUL
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindPopupGroupData(Element* aElement,
+ nsStyleContext* /* unused */)
+{
+ if (!aElement->IsRootOfNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sPopupSetData =
+ SIMPLE_XUL_FCDATA(NS_NewPopupSetFrame);
+ return &sPopupSetData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData
+nsCSSFrameConstructor::sXULTextBoxData = SIMPLE_XUL_FCDATA(NS_NewTextBoxFrame);
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULLabelData(Element* aElement,
+ nsStyleContext* /* unused */)
+{
+ if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+ return &sXULTextBoxData;
+ }
+
+ static const FrameConstructionData sLabelData =
+ SIMPLE_XUL_FCDATA(NS_NewXULLabelFrame);
+ return &sLabelData;
+}
+
+static nsIFrame*
+NS_NewXULDescriptionFrame(nsIPresShell* aPresShell, nsStyleContext *aContext)
+{
+ // XXXbz do we really need to set up the block formatting context root? If the
+ // parent is not a block we'll get it anyway, and if it is, do we want it?
+ return NS_NewBlockFormattingContext(aPresShell, aContext);
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULDescriptionData(Element* aElement,
+ nsStyleContext* /* unused */)
+{
+ if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+ return &sXULTextBoxData;
+ }
+
+ static const FrameConstructionData sDescriptionData =
+ SIMPLE_XUL_FCDATA(NS_NewXULDescriptionFrame);
+ return &sDescriptionData;
+}
+
+#ifdef XP_MACOSX
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULMenubarData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ nsCOMPtr<nsIDocShell> treeItem =
+ aStyleContext->PresContext()->GetDocShell();
+ if (treeItem && nsIDocShellTreeItem::typeChrome == treeItem->ItemType()) {
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ treeItem->GetParent(getter_AddRefs(parent));
+ if (!parent) {
+ // This is the root. Suppress the menubar, since on Mac
+ // window menus are not attached to the window.
+ static const FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
+ return &sSuppressData;
+ }
+ }
+
+ static const FrameConstructionData sMenubarData =
+ SIMPLE_XUL_FCDATA(NS_NewMenuBarFrame);
+ return &sMenubarData;
+}
+#endif /* XP_MACOSX */
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULListBoxBodyData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ if (aStyleContext->StyleDisplay()->mDisplay !=
+ StyleDisplay::XulGridGroup) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sListBoxBodyData =
+ SCROLLABLE_XUL_FCDATA(NS_NewListBoxBodyFrame);
+ return &sListBoxBodyData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULListItemData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ if (aStyleContext->StyleDisplay()->mDisplay != StyleDisplay::XulGridLine) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sListItemData =
+ SCROLLABLE_XUL_FCDATA(NS_NewListItemFrame);
+ return &sListItemData;
+}
+
+#endif /* MOZ_XUL */
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULDisplayData(const nsStyleDisplay* aDisplay,
+ Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ static const FrameConstructionDataByDisplay sXULDisplayData[] = {
+ SCROLLABLE_ABSPOS_CONTAINER_XUL_DISPLAY_CREATE(StyleDisplay::Box,
+ NS_NewBoxFrame),
+ SCROLLABLE_ABSPOS_CONTAINER_XUL_DISPLAY_CREATE(StyleDisplay::InlineBox,
+ NS_NewBoxFrame),
+#ifdef MOZ_XUL
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::XulGrid, NS_NewGridBoxFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::InlineXulGrid, NS_NewGridBoxFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::XulGridGroup,
+ NS_NewGridRowGroupFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::XulGridLine,
+ NS_NewGridRowLeafFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::Stack, NS_NewStackFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::InlineStack, NS_NewStackFrame),
+ SIMPLE_XUL_DISPLAY_CREATE(StyleDisplay::Deck, NS_NewDeckFrame),
+ SCROLLABLE_XUL_DISPLAY_CREATE(StyleDisplay::Groupbox, NS_NewGroupBoxFrame),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Popup,
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_IS_POPUP |
+ FCDATA_SKIP_ABSPOS_PUSH, NS_NewMenuPopupFrame))
+#endif /* MOZ_XUL */
+ };
+
+ if (aDisplay->mDisplay < StyleDisplay::Box) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(aDisplay->mDisplay <= StyleDisplay::Popup,
+ "Someone added a new display value?");
+
+ const FrameConstructionDataByDisplay& data =
+ sXULDisplayData[size_t(aDisplay->mDisplay) - size_t(StyleDisplay::Box)];
+ MOZ_ASSERT(aDisplay->mDisplay == data.mDisplay,
+ "Did someone mess with the order?");
+
+ return &data.mData;
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::BeginBuildingScrollFrame(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aContentStyle,
+ nsContainerFrame* aParentFrame,
+ nsIAtom* aScrolledPseudo,
+ bool aIsRoot,
+ nsContainerFrame*& aNewFrame)
+{
+ nsContainerFrame* gfxScrollFrame = aNewFrame;
+
+ nsFrameItems anonymousItems;
+
+ RefPtr<nsStyleContext> contentStyle = aContentStyle;
+
+ if (!gfxScrollFrame) {
+ // Build a XULScrollFrame when the child is a box, otherwise an
+ // HTMLScrollFrame
+ // XXXbz this is the lone remaining consumer of IsXULDisplayType.
+ // I wonder whether we can eliminate that somehow.
+ const nsStyleDisplay* displayStyle = aContentStyle->StyleDisplay();
+ if (IsXULDisplayType(displayStyle)) {
+ gfxScrollFrame = NS_NewXULScrollFrame(mPresShell, contentStyle, aIsRoot,
+ displayStyle->mDisplay == StyleDisplay::Stack ||
+ displayStyle->mDisplay == StyleDisplay::InlineStack);
+ } else {
+ gfxScrollFrame = NS_NewHTMLScrollFrame(mPresShell, contentStyle, aIsRoot);
+ }
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, gfxScrollFrame);
+ }
+
+ // if there are any anonymous children for the scroll frame, create
+ // frames for them.
+ // Pass a null pending binding: we don't care how constructors for any of
+ // this anonymous content order with anything else. It's never been
+ // consistent anyway.
+ CreateAnonymousFrames(aState, aContent, gfxScrollFrame, nullptr,
+ anonymousItems);
+
+ aNewFrame = gfxScrollFrame;
+
+ // we used the style that was passed in. So resolve another one.
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+ RefPtr<nsStyleContext> scrolledChildStyle =
+ styleSet->ResolveAnonymousBoxStyle(aScrolledPseudo, contentStyle);
+
+ if (gfxScrollFrame) {
+ gfxScrollFrame->SetInitialChildList(kPrincipalList, anonymousItems);
+ }
+
+ return scrolledChildStyle.forget();
+}
+
+void
+nsCSSFrameConstructor::FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame,
+ nsIFrame* aScrolledFrame)
+{
+ nsFrameList scrolled(aScrolledFrame, aScrolledFrame);
+ aScrollFrame->AppendFrames(kPrincipalList, scrolled);
+}
+
+/**
+ * Called to wrap a gfx scrollframe around a frame. The hierarchy will look like this
+ *
+ * ------- for gfx scrollbars ------
+ *
+ *
+ * ScrollFrame
+ * ^
+ * |
+ * Frame (scrolled frame you passed in)
+ *
+ *
+ *-----------------------------------
+ * LEGEND:
+ *
+ * ScrollFrame: This is a frame that manages gfx cross platform frame based scrollbars.
+ *
+ * @param aContent the content node of the child to wrap.
+ * @param aScrolledFrame The frame of the content to wrap. This should not be
+ * Initialized. This method will initialize it with a scrolled pseudo
+ * and no nsIContent. The content will be attached to the scrollframe
+ * returned.
+ * @param aContentStyle the style context that has already been resolved for the content being passed in.
+ *
+ * @param aParentFrame The parent to attach the scroll frame to
+ *
+ * @param aNewFrame The new scrollframe or gfx scrollframe that we create. It will contain the
+ * scrolled frame you passed in. (returned)
+ * If this is not null, we'll just use it
+ * @param aScrolledContentStyle the style that was resolved for the scrolled frame. (returned)
+ */
+void
+nsCSSFrameConstructor::BuildScrollFrame(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aContentStyle,
+ nsIFrame* aScrolledFrame,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame*& aNewFrame)
+{
+ RefPtr<nsStyleContext> scrolledContentStyle =
+ BeginBuildingScrollFrame(aState, aContent, aContentStyle, aParentFrame,
+ nsCSSAnonBoxes::scrolledContent,
+ false, aNewFrame);
+
+ aScrolledFrame->SetStyleContextWithoutNotification(scrolledContentStyle);
+ InitAndRestoreFrame(aState, aContent, aNewFrame, aScrolledFrame);
+
+ FinishBuildingScrollFrame(aNewFrame, aScrolledFrame);
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay* aDisplay,
+ Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ static_assert(eParentTypeCount < (1 << (32 - FCDATA_PARENT_TYPE_OFFSET)),
+ "Check eParentTypeCount should not overflow");
+
+ // The style system ensures that floated and positioned frames are
+ // block-level.
+ NS_ASSERTION(!(aDisplay->IsFloatingStyle() ||
+ aDisplay->IsAbsolutelyPositionedStyle()) ||
+ aDisplay->IsBlockOutsideStyle() ||
+ aDisplay->mDisplay == StyleDisplay::Contents,
+ "Style system did not apply CSS2.1 section 9.7 fixups");
+
+ // If this is "body", try propagating its scroll style to the viewport
+ // Note that we need to do this even if the body is NOT scrollable;
+ // it might have dynamically changed from scrollable to not scrollable,
+ // and that might need to be propagated.
+ // XXXbz is this the right place to do this? If this code moves,
+ // make this function static.
+ bool propagatedScrollToViewport = false;
+ if (aElement->IsHTMLElement(nsGkAtoms::body)) {
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ propagatedScrollToViewport =
+ presContext->UpdateViewportScrollbarStylesOverride() == aElement;
+ }
+ }
+
+ NS_ASSERTION(!propagatedScrollToViewport ||
+ !mPresShell->GetPresContext()->IsPaginated(),
+ "Shouldn't propagate scroll in paginated contexts");
+
+ if (aDisplay->IsBlockInsideStyle()) {
+ // If the frame is a block-level frame and is scrollable, then wrap it in a
+ // scroll frame. Except we don't want to do that for paginated contexts for
+ // frames that are block-outside and aren't frames for native anonymous stuff.
+ // XXX Ignore tables for the time being (except caption)
+ const uint32_t kCaptionCtorFlags =
+ FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable);
+ bool caption = aDisplay->mDisplay == StyleDisplay::TableCaption;
+ bool suppressScrollFrame = false;
+ bool needScrollFrame = aDisplay->IsScrollableOverflow() &&
+ !propagatedScrollToViewport;
+ if (needScrollFrame) {
+ suppressScrollFrame = mPresShell->GetPresContext()->IsPaginated() &&
+ aDisplay->IsBlockOutsideStyle() &&
+ !aElement->IsInNativeAnonymousSubtree();
+ if (!suppressScrollFrame) {
+ static const FrameConstructionData sScrollableBlockData[2] =
+ { FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructScrollableBlock),
+ FULL_CTOR_FCDATA(kCaptionCtorFlags,
+ &nsCSSFrameConstructor::ConstructScrollableBlock) };
+ return &sScrollableBlockData[caption];
+ }
+
+ // If the scrollable frame would have propagated its scrolling to the
+ // viewport, we still want to construct a regular block rather than a
+ // scrollframe so that it paginates correctly, but we don't want to set
+ // the bit on the block that tells it to clip at paint time.
+ if (mPresShell->GetPresContext()->
+ ElementWouldPropagateScrollbarStyles(aElement)) {
+ suppressScrollFrame = false;
+ }
+ }
+
+ // Handle various non-scrollable blocks.
+ static const FrameConstructionData sNonScrollableBlockData[2][2] = {
+ { FULL_CTOR_FCDATA(0,
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock),
+ FULL_CTOR_FCDATA(kCaptionCtorFlags,
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock) },
+ { FULL_CTOR_FCDATA(FCDATA_FORCED_NON_SCROLLABLE_BLOCK,
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock),
+ FULL_CTOR_FCDATA(FCDATA_FORCED_NON_SCROLLABLE_BLOCK | kCaptionCtorFlags,
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock) }
+ };
+ return &sNonScrollableBlockData[suppressScrollFrame][caption];
+ }
+
+ // If this is for a <body> node and we've propagated the scroll-frame to the
+ // viewport, we need to make sure not to add another layer of scrollbars, so
+ // we use a different FCData struct without FCDATA_MAY_NEED_SCROLLFRAME.
+ if (propagatedScrollToViewport && aDisplay->IsScrollableOverflow()) {
+ if (aDisplay->mDisplay == StyleDisplay::Flex ||
+ aDisplay->mDisplay == StyleDisplay::WebkitBox) {
+ static const FrameConstructionData sNonScrollableFlexData =
+ FCDATA_DECL(0, NS_NewFlexContainerFrame);
+ return &sNonScrollableFlexData;
+ }
+ if (aDisplay->mDisplay == StyleDisplay::Grid) {
+ static const FrameConstructionData sNonScrollableGridData =
+ FCDATA_DECL(0, NS_NewGridContainerFrame);
+ return &sNonScrollableGridData;
+ }
+ }
+
+ // NOTE: Make sure to keep this up to date with the StyleDisplay definition!
+ static const FrameConstructionDataByDisplay sDisplayData[] = {
+ FCDATA_FOR_DISPLAY(StyleDisplay::None, UNREACHABLE_FCDATA()),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Block, UNREACHABLE_FCDATA()),
+ // To keep the hash table small don't add inline frames (they're
+ // typically things like FONT and B), because we can quickly
+ // find them if we need to.
+ // XXXbz the "quickly" part is a bald-faced lie!
+ FCDATA_FOR_DISPLAY(StyleDisplay::Inline,
+ FULL_CTOR_FCDATA(FCDATA_IS_INLINE | FCDATA_IS_LINE_PARTICIPANT,
+ &nsCSSFrameConstructor::ConstructInline)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::InlineBlock, UNREACHABLE_FCDATA()),
+ FCDATA_FOR_DISPLAY(StyleDisplay::ListItem, UNREACHABLE_FCDATA()),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Table,
+ FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructTable)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::InlineTable,
+ FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructTable)),
+ // NOTE: In the unlikely event that we add another table-part here that has
+ // a desired-parent-type (& hence triggers table fixup), we'll need to also
+ // update the flexbox chunk in nsStyleContext::ApplyStyleFixups().
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableRowGroup,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableColumn,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeColGroup),
+ &nsCSSFrameConstructor::ConstructTableCol)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableColumnGroup,
+ FCDATA_DECL(FCDATA_IS_TABLE_PART | FCDATA_DISALLOW_OUT_OF_FLOW |
+ FCDATA_SKIP_ABSPOS_PUSH |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ NS_NewTableColGroupFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableHeaderGroup,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableFooterGroup,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableRow,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRowGroup),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableCell,
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRow),
+ &nsCSSFrameConstructor::ConstructTableCell)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::TableCaption, UNREACHABLE_FCDATA()),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Flex,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::InlineFlex,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Grid,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewGridContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::InlineGrid,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewGridContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Ruby,
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT, NS_NewRubyFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::RubyBase,
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer),
+ NS_NewRubyBaseFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::RubyBaseContainer,
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby),
+ NS_NewRubyBaseContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::RubyText,
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyTextContainer),
+ NS_NewRubyTextFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::RubyTextContainer,
+ FCDATA_DECL(FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby),
+ NS_NewRubyTextContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::Contents,
+ FULL_CTOR_FCDATA(FCDATA_IS_CONTENTS, nullptr/*never called*/)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::WebkitBox,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
+ FCDATA_FOR_DISPLAY(StyleDisplay::WebkitInlineBox,
+ FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewFlexContainerFrame)),
+ };
+ static_assert(ArrayLength(sDisplayData) == size_t(StyleDisplay::WebkitInlineBox) + 1,
+ "Be sure to update sDisplayData if you touch StyleDisplay");
+
+ MOZ_ASSERT(size_t(aDisplay->mDisplay) < ArrayLength(sDisplayData),
+ "XUL display data should have already been handled");
+
+ // See the mDisplay fixup code in nsRuleNode::ComputeDisplayData.
+ MOZ_ASSERT(aDisplay->mDisplay != StyleDisplay::Contents ||
+ !aElement->IsRootOfNativeAnonymousSubtree(),
+ "display:contents on anonymous content is unsupported");
+
+ const FrameConstructionDataByDisplay& data =
+ sDisplayData[size_t(aDisplay->mDisplay)];
+
+ MOZ_ASSERT(data.mDisplay == aDisplay->mDisplay,
+ "Someone messed up the order in the display values");
+
+ return &data.mData;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ return ConstructScrollableBlockWithConstructor(aState, aItem, aParentFrame,
+ aDisplay, aFrameItems,
+ NS_NewBlockFormattingContext);
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructScrollableBlockWithConstructor(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems,
+ BlockFrameCreationFunc aConstructor)
+{
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ nsContainerFrame* newFrame = nullptr;
+ RefPtr<nsStyleContext> scrolledContentStyle
+ = BeginBuildingScrollFrame(aState, content, styleContext,
+ aState.GetGeometricParent(aDisplay, aParentFrame),
+ nsCSSAnonBoxes::scrolledContent,
+ false, newFrame);
+
+ // Create our block frame
+ // pass a temporary stylecontext, the correct one will be set later
+ nsContainerFrame* scrolledFrame = aConstructor(mPresShell, styleContext);
+
+ // Make sure to AddChild before we call ConstructBlock so that we
+ // end up before our descendants in fixed-pos lists as needed.
+ aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
+
+ nsFrameItems blockItem;
+ ConstructBlock(aState, content, newFrame, newFrame, scrolledContentStyle,
+ &scrolledFrame, blockItem,
+ aDisplay->IsAbsPosContainingBlock(newFrame) ? newFrame : nullptr,
+ aItem.mPendingBinding);
+
+ MOZ_ASSERT(blockItem.OnlyChild() == scrolledFrame,
+ "Scrollframe's frameItems should be exactly the scrolled frame!");
+ FinishBuildingScrollFrame(newFrame, scrolledFrame);
+
+ return newFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructNonScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ return ConstructNonScrollableBlockWithConstructor(aState, aItem, aParentFrame,
+ aDisplay, aFrameItems,
+ NS_NewBlockFrame);
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructNonScrollableBlockWithConstructor(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems,
+ BlockFrameCreationFunc aConstructor)
+{
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ // We want a block formatting context root in paginated contexts for
+ // every block that would be scrollable in a non-paginated context.
+ // We mark our blocks with a bit here if this condition is true, so
+ // we can check it later in nsFrame::ApplyPaginatedOverflowClipping.
+ bool clipPaginatedOverflow =
+ (aItem.mFCData->mBits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) != 0;
+ nsFrameState flags = nsFrameState(0);
+ if ((aDisplay->IsAbsolutelyPositionedStyle() ||
+ aDisplay->IsFloatingStyle() ||
+ StyleDisplay::InlineBlock == aDisplay->mDisplay ||
+ clipPaginatedOverflow) &&
+ !aParentFrame->IsSVGText()) {
+ flags = NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT;
+ if (clipPaginatedOverflow) {
+ flags |= NS_BLOCK_CLIP_PAGINATED_OVERFLOW;
+ }
+ }
+
+ nsContainerFrame* newFrame = aConstructor(mPresShell, styleContext);
+ newFrame->AddStateBits(flags);
+ ConstructBlock(aState, aItem.mContent,
+ aState.GetGeometricParent(aDisplay, aParentFrame),
+ aParentFrame, styleContext, &newFrame,
+ aFrameItems,
+ aDisplay->IsAbsPosContainingBlock(newFrame) ? newFrame : nullptr,
+ aItem.mPendingBinding);
+ return newFrame;
+}
+
+
+void
+nsCSSFrameConstructor::InitAndRestoreFrame(const nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aNewFrame,
+ bool aAllowCounters)
+{
+ NS_PRECONDITION(mUpdateCount != 0,
+ "Should be in an update while creating frames");
+
+ MOZ_ASSERT(aNewFrame, "Null frame cannot be initialized");
+
+ // Initialize the frame
+ aNewFrame->Init(aContent, aParentFrame, nullptr);
+ aNewFrame->AddStateBits(aState.mAdditionalStateBits);
+
+ if (aState.mFrameState) {
+ // Restore frame state for just the newly created frame.
+ RestoreFrameStateFor(aNewFrame, aState.mFrameState);
+ }
+
+ if (aAllowCounters &&
+ mCounterManager.AddCounterResetsAndIncrements(aNewFrame)) {
+ CountersDirty();
+ }
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::ResolveStyleContext(nsIFrame* aParentFrame,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState)
+{
+ MOZ_ASSERT(aContainer, "Must have parent here");
+ // XXX uncomment when bug 1089223 is fixed:
+ // MOZ_ASSERT(aContainer == aChild->GetFlattenedTreeParent());
+ nsStyleContext* parentStyleContext = GetDisplayContentsStyleFor(aContainer);
+ if (MOZ_LIKELY(!parentStyleContext)) {
+ aParentFrame = nsFrame::CorrectStyleParentFrame(aParentFrame, nullptr);
+ if (aParentFrame) {
+ MOZ_ASSERT(aParentFrame->GetContent() == aContainer);
+ // Resolve the style context based on the content object and the parent
+ // style context
+ parentStyleContext = aParentFrame->StyleContext();
+ } else {
+ // Perhaps aParentFrame is a canvasFrame and we're replicating
+ // fixed-pos frames.
+ // XXX should we create a way to tell ConstructFrame which style
+ // context to use, and pass it the style context for the
+ // previous page's fixed-pos frame?
+ }
+ }
+
+ return ResolveStyleContext(parentStyleContext, aChild, aState);
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::ResolveStyleContext(nsIFrame* aParentFrame,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState)
+{
+ return ResolveStyleContext(aParentFrame, aChild->GetFlattenedTreeParent(), aChild, aState);
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::ResolveStyleContext(const InsertionPoint& aInsertion,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState)
+{
+ return ResolveStyleContext(aInsertion.mParentFrame, aInsertion.mContainer,
+ aChild, aState);
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::ResolveStyleContext(nsStyleContext* aParentStyleContext,
+ nsIContent* aContent,
+ nsFrameConstructorState* aState)
+{
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+ aContent->OwnerDoc()->FlushPendingLinkUpdates();
+
+ RefPtr<nsStyleContext> result;
+ if (aContent->IsElement()) {
+ if (aState) {
+ result = styleSet->ResolveStyleFor(aContent->AsElement(),
+ aParentStyleContext,
+ aState->mTreeMatchContext);
+ } else {
+ result = styleSet->ResolveStyleFor(aContent->AsElement(),
+ aParentStyleContext);
+ }
+ } else {
+ NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
+ "shouldn't waste time creating style contexts for "
+ "comments and processing instructions");
+ result = styleSet->ResolveStyleForText(aContent, aParentStyleContext);
+ }
+
+ // ServoRestyleManager does not handle transitions yet, and when it does
+ // it probably won't need to track reframed style contexts to start
+ // transitions correctly.
+ if (mozilla::RestyleManager* geckoRM = RestyleManager()->GetAsGecko()) {
+ RestyleManager::ReframingStyleContexts* rsc =
+ geckoRM->GetReframingStyleContexts();
+ if (rsc) {
+ nsStyleContext* oldStyleContext =
+ rsc->Get(aContent, CSSPseudoElementType::NotPseudo);
+ nsPresContext* presContext = mPresShell->GetPresContext();
+ if (oldStyleContext) {
+ RestyleManager::TryInitiatingTransition(presContext, aContent,
+ oldStyleContext, &result);
+ } else if (aContent->IsElement()) {
+ presContext->TransitionManager()->
+ PruneCompletedTransitions(aContent->AsElement(),
+ CSSPseudoElementType::NotPseudo, result);
+ }
+ }
+ }
+
+ return result.forget();
+}
+
+// MathML Mod - RBS
+void
+nsCSSFrameConstructor::FlushAccumulatedBlock(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aBlockItems,
+ nsFrameItems& aNewItems)
+{
+ if (aBlockItems.IsEmpty()) {
+ // Nothing to do
+ return;
+ }
+
+ nsIAtom* anonPseudo = nsCSSAnonBoxes::mozMathMLAnonymousBlock;
+
+ nsStyleContext* parentContext =
+ nsFrame::CorrectStyleParentFrame(aParentFrame,
+ anonPseudo)->StyleContext();
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+ RefPtr<nsStyleContext> blockContext;
+ blockContext = styleSet->
+ ResolveAnonymousBoxStyle(anonPseudo, parentContext);
+
+
+ // then, create a block frame that will wrap the child frames. Make it a
+ // MathML frame so that Get(Absolute/Float)ContainingBlockFor know that this
+ // is not a suitable block.
+ nsContainerFrame* blockFrame =
+ NS_NewMathMLmathBlockFrame(mPresShell, blockContext);
+ blockFrame->AddStateBits(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT);
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, blockFrame);
+ ReparentFrames(this, blockFrame, aBlockItems);
+ // abs-pos and floats are disabled in MathML children so we don't have to
+ // worry about messing up those.
+ blockFrame->SetInitialChildList(kPrincipalList, aBlockItems);
+ NS_ASSERTION(aBlockItems.IsEmpty(), "What happened?");
+ aBlockItems.Clear();
+ aNewItems.AddChild(blockFrame);
+}
+
+// Only <math> elements can be floated or positioned. All other MathML
+// should be in-flow.
+#define SIMPLE_MATHML_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, \
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_FORCE_NULL_ABSPOS_CONTAINER | \
+ FCDATA_WRAP_KIDS_IN_BLOCKS, _func) }
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindMathMLData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsStyleContext* aStyleContext)
+{
+ // Make sure that we remain confined in the MathML world
+ if (aNameSpaceID != kNameSpaceID_MathML)
+ return nullptr;
+
+ // Handle <math> specially, because it sometimes produces inlines
+ if (aTag == nsGkAtoms::math) {
+ // This needs to match the test in EnsureBlockDisplay in
+ // nsRuleNode.cpp. Though the behavior here for the display:table
+ // case is pretty weird...
+ if (aStyleContext->StyleDisplay()->IsBlockOutsideStyle()) {
+ static const FrameConstructionData sBlockMathData =
+ FCDATA_DECL(FCDATA_FORCE_NULL_ABSPOS_CONTAINER |
+ FCDATA_WRAP_KIDS_IN_BLOCKS,
+ NS_NewMathMLmathBlockFrame);
+ return &sBlockMathData;
+ }
+
+ static const FrameConstructionData sInlineMathData =
+ FCDATA_DECL(FCDATA_FORCE_NULL_ABSPOS_CONTAINER |
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_WRAP_KIDS_IN_BLOCKS,
+ NS_NewMathMLmathInlineFrame);
+ return &sInlineMathData;
+ }
+
+
+ static const FrameConstructionDataByTag sMathMLData[] = {
+ SIMPLE_MATHML_CREATE(annotation_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(annotation_xml_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mi_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mn_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(ms_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mtext_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mo_, NS_NewMathMLmoFrame),
+ SIMPLE_MATHML_CREATE(mfrac_, NS_NewMathMLmfracFrame),
+ SIMPLE_MATHML_CREATE(msup_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(msub_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(msubsup_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(munder_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(mover_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(munderover_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(mphantom_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mpadded_, NS_NewMathMLmpaddedFrame),
+ SIMPLE_MATHML_CREATE(mspace_, NS_NewMathMLmspaceFrame),
+ SIMPLE_MATHML_CREATE(none, NS_NewMathMLmspaceFrame),
+ SIMPLE_MATHML_CREATE(mprescripts_, NS_NewMathMLmspaceFrame),
+ SIMPLE_MATHML_CREATE(mfenced_, NS_NewMathMLmfencedFrame),
+ SIMPLE_MATHML_CREATE(mmultiscripts_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(mstyle_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(msqrt_, NS_NewMathMLmsqrtFrame),
+ SIMPLE_MATHML_CREATE(mroot_, NS_NewMathMLmrootFrame),
+ SIMPLE_MATHML_CREATE(maction_, NS_NewMathMLmactionFrame),
+ SIMPLE_MATHML_CREATE(mrow_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(merror_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(menclose_, NS_NewMathMLmencloseFrame),
+ SIMPLE_MATHML_CREATE(semantics_, NS_NewMathMLsemanticsFrame)
+ };
+
+ return FindDataByTag(aTag, aElement, aStyleContext, sMathMLData,
+ ArrayLength(sMathMLData));
+}
+
+
+nsContainerFrame*
+nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems,
+ ContainerFrameCreationFunc aConstructor,
+ ContainerFrameCreationFunc aInnerConstructor,
+ nsICSSAnonBoxPseudo* aInnerPseudo,
+ bool aCandidateRootFrame)
+{
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ // Create the outer frame:
+ nsContainerFrame* newFrame = aConstructor(mPresShell, styleContext);
+
+ InitAndRestoreFrame(aState, content,
+ aCandidateRootFrame ?
+ aState.GetGeometricParent(styleContext->StyleDisplay(),
+ aParentFrame) :
+ aParentFrame,
+ newFrame);
+
+ // Create the pseudo SC for the anonymous wrapper child as a child of the SC:
+ RefPtr<nsStyleContext> scForAnon;
+ scForAnon = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(aInnerPseudo, styleContext);
+
+ // Create the anonymous inner wrapper frame
+ nsContainerFrame* innerFrame = aInnerConstructor(mPresShell, scForAnon);
+
+ InitAndRestoreFrame(aState, content, newFrame, innerFrame);
+
+ // Put the newly created frames into the right child list
+ SetInitialSingleChild(newFrame, innerFrame);
+
+ aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame,
+ aCandidateRootFrame, aCandidateRootFrame);
+
+ if (!mRootElementFrame && aCandidateRootFrame) {
+ // The frame we're constructing will be the root element frame.
+ // Set mRootElementFrame before processing children.
+ mRootElementFrame = newFrame;
+ }
+
+ nsFrameItems childItems;
+
+ // Process children
+ NS_ASSERTION(aItem.mAnonChildren.IsEmpty(),
+ "nsIAnonymousContentCreator::CreateAnonymousContent should not "
+ "be implemented for frames for which we explicitly create an "
+ "anonymous child to wrap its child frames");
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(aState, aItem.mChildItems,
+ innerFrame, childItems);
+ } else {
+ ProcessChildren(aState, content, styleContext, innerFrame,
+ true, childItems, false, aItem.mPendingBinding);
+ }
+
+ // Set the inner wrapper frame's initial primary list
+ innerFrame->SetInitialChildList(kPrincipalList, childItems);
+
+ return newFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructOuterSVG(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ return ConstructFrameWithAnonymousChild(
+ aState, aItem, aParentFrame, aFrameItems,
+ NS_NewSVGOuterSVGFrame, NS_NewSVGOuterSVGAnonChildFrame,
+ nsCSSAnonBoxes::mozSVGOuterSVGAnonChild, true);
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructMarker(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ return ConstructFrameWithAnonymousChild(
+ aState, aItem, aParentFrame, aFrameItems,
+ NS_NewSVGMarkerFrame, NS_NewSVGMarkerAnonChildFrame,
+ nsCSSAnonBoxes::mozSVGMarkerAnonChild, false);
+}
+
+// Only outer <svg> elements can be floated or positioned. All other SVG
+// should be in-flow.
+#define SIMPLE_SVG_FCDATA(_func) \
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_SKIP_ABSPOS_PUSH | \
+ FCDATA_DISALLOW_GENERATED_CONTENT, _func)
+#define SIMPLE_SVG_CREATE(_tag, _func) \
+ { &nsGkAtoms::_tag, SIMPLE_SVG_FCDATA(_func) }
+
+static bool
+IsFilterPrimitiveChildTag(const nsIAtom* aTag)
+{
+ return aTag == nsGkAtoms::feDistantLight ||
+ aTag == nsGkAtoms::fePointLight ||
+ aTag == nsGkAtoms::feSpotLight ||
+ aTag == nsGkAtoms::feFuncR ||
+ aTag == nsGkAtoms::feFuncG ||
+ aTag == nsGkAtoms::feFuncB ||
+ aTag == nsGkAtoms::feFuncA ||
+ aTag == nsGkAtoms::feMergeNode;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindSVGData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsIFrame* aParentFrame,
+ bool aIsWithinSVGText,
+ bool aAllowsTextPathChild,
+ nsStyleContext* aStyleContext)
+{
+ if (aNameSpaceID != kNameSpaceID_SVG) {
+ return nullptr;
+ }
+
+ static const FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
+ static const FrameConstructionData sContainerData =
+ SIMPLE_SVG_FCDATA(NS_NewSVGContainerFrame);
+
+ bool parentIsSVG = aIsWithinSVGText;
+ nsIContent* parentContent =
+ aParentFrame ? aParentFrame->GetContent() : nullptr;
+ // XXXbz should this really be based on the XBL-resolved tag of the parent
+ // frame's content? Should it not be based on the type of the parent frame
+ // (e.g. whether it's an SVG frame)?
+ if (parentContent) {
+ int32_t parentNSID;
+ nsIAtom* parentTag =
+ parentContent->OwnerDoc()->BindingManager()->
+ ResolveTag(parentContent, &parentNSID);
+
+ // It's not clear whether the SVG spec intends to allow any SVG
+ // content within svg:foreignObject at all (SVG 1.1, section
+ // 23.2), but if it does, it better be svg:svg. So given that
+ // we're allowing it, treat it as a non-SVG parent.
+ parentIsSVG = parentNSID == kNameSpaceID_SVG &&
+ parentTag != nsGkAtoms::foreignObject;
+ }
+
+ if ((aTag != nsGkAtoms::svg && !parentIsSVG) ||
+ (aTag == nsGkAtoms::desc || aTag == nsGkAtoms::title ||
+ aTag == nsGkAtoms::metadata)) {
+ // Sections 5.1 and G.4 of SVG 1.1 say that SVG elements other than
+ // svg:svg not contained within svg:svg are incorrect, although they
+ // don't seem to specify error handling. Ignore them, since many of
+ // our frame classes can't deal. It *may* be that the document
+ // should at that point be considered in error according to F.2, but
+ // it's hard to tell.
+ //
+ // Style mutation can't change this situation, so don't bother
+ // adding to the undisplayed content map.
+ //
+ // We don't currently handle any UI for desc/title/metadata
+ return &sSuppressData;
+ }
+
+ // We don't need frames for animation elements
+ if (aElement->IsNodeOfType(nsINode::eANIMATION)) {
+ return &sSuppressData;
+ }
+
+ if (aTag == nsGkAtoms::svg && !parentIsSVG) {
+ // We need outer <svg> elements to have an nsSVGOuterSVGFrame regardless
+ // of whether they fail conditional processing attributes, since various
+ // SVG frames assume that one exists. We handle the non-rendering
+ // of failing outer <svg> element contents like <switch> statements,
+ // and do the PassesConditionalProcessingTests call in
+ // nsSVGOuterSVGFrame::Init.
+ static const FrameConstructionData sOuterSVGData =
+ FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructOuterSVG);
+ return &sOuterSVGData;
+ }
+
+ if (aTag == nsGkAtoms::marker) {
+ static const FrameConstructionData sMarkerSVGData =
+ FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructMarker);
+ return &sMarkerSVGData;
+ }
+
+ nsCOMPtr<SVGTests> tests(do_QueryInterface(aElement));
+ if (tests && !tests->PassesConditionalProcessingTests()) {
+ // Elements with failing conditional processing attributes never get
+ // rendered. Note that this is not where we select which frame in a
+ // <switch> to render! That happens in nsSVGSwitchFrame::PaintSVG.
+ if (aIsWithinSVGText) {
+ // SVGTextFrame doesn't handle conditional processing attributes,
+ // so don't create frames for descendants of <text> with failing
+ // attributes. We need frames not to be created so that text layout
+ // is correct.
+ return &sSuppressData;
+ }
+ // If we're not inside <text>, create an nsSVGContainerFrame (which is a
+ // frame that doesn't render) so that paint servers can still be referenced,
+ // even if they live inside an element with failing conditional processing
+ // attributes.
+ return &sContainerData;
+ }
+
+ // Ensure that a stop frame is a child of a gradient and that gradients
+ // can only have stop children.
+ bool parentIsGradient = aParentFrame &&
+ (aParentFrame->GetType() == nsGkAtoms::svgLinearGradientFrame ||
+ aParentFrame->GetType() == nsGkAtoms::svgRadialGradientFrame);
+ bool stop = (aTag == nsGkAtoms::stop);
+ if ((parentIsGradient && !stop) ||
+ (!parentIsGradient && stop)) {
+ return &sSuppressData;
+ }
+
+ // Prevent bad frame types being children of filters or parents of filter
+ // primitives. If aParentFrame is null, we know that the frame that will
+ // be created will be an nsInlineFrame, so it can never be a filter.
+ bool parentIsFilter = aParentFrame &&
+ aParentFrame->GetType() == nsGkAtoms::svgFilterFrame;
+ bool filterPrimitive = aElement->IsNodeOfType(nsINode::eFILTER);
+ if ((parentIsFilter && !filterPrimitive) ||
+ (!parentIsFilter && filterPrimitive)) {
+ return &sSuppressData;
+ }
+
+ // Prevent bad frame types being children of filter primitives or parents of
+ // filter primitive children. If aParentFrame is null, we know that the frame
+ // that will be created will be an nsInlineFrame, so it can never be a filter
+ // primitive.
+ bool parentIsFEContainerFrame = aParentFrame &&
+ aParentFrame->GetType() == nsGkAtoms::svgFEContainerFrame;
+ if ((parentIsFEContainerFrame && !IsFilterPrimitiveChildTag(aTag)) ||
+ (!parentIsFEContainerFrame && IsFilterPrimitiveChildTag(aTag))) {
+ return &sSuppressData;
+ }
+
+ // Special cases for text/tspan/textPath, because the kind of frame
+ // they get depends on the parent frame. We ignore 'a' elements when
+ // determining the parent, however.
+ if (aIsWithinSVGText) {
+ // If aIsWithinSVGText is true, then we know that the "SVG text uses
+ // CSS frames" pref was true when this SVG fragment was first constructed.
+
+ // We don't use ConstructInline because we want different behavior
+ // for generated content.
+ static const FrameConstructionData sTSpanData =
+ FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW |
+ FCDATA_SKIP_ABSPOS_PUSH |
+ FCDATA_DISALLOW_GENERATED_CONTENT |
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_IS_INLINE |
+ FCDATA_USE_CHILD_ITEMS,
+ NS_NewInlineFrame);
+ if (aTag == nsGkAtoms::textPath) {
+ if (aAllowsTextPathChild) {
+ return &sTSpanData;
+ }
+ } else if (aTag == nsGkAtoms::tspan ||
+ aTag == nsGkAtoms::a) {
+ return &sTSpanData;
+ }
+ return &sSuppressData;
+ } else if (aTag == nsGkAtoms::text) {
+ static const FrameConstructionData sTextData =
+ FCDATA_WITH_WRAPPING_BLOCK(FCDATA_DISALLOW_OUT_OF_FLOW |
+ FCDATA_ALLOW_BLOCK_STYLES,
+ NS_NewSVGTextFrame,
+ nsCSSAnonBoxes::mozSVGText);
+ return &sTextData;
+ } else if (aTag == nsGkAtoms::tspan ||
+ aTag == nsGkAtoms::textPath) {
+ return &sSuppressData;
+ }
+
+ static const FrameConstructionDataByTag sSVGData[] = {
+ SIMPLE_SVG_CREATE(svg, NS_NewSVGInnerSVGFrame),
+ SIMPLE_SVG_CREATE(g, NS_NewSVGGFrame),
+ SIMPLE_SVG_CREATE(svgSwitch, NS_NewSVGSwitchFrame),
+ SIMPLE_SVG_CREATE(polygon, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(polyline, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(circle, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(ellipse, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(line, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(rect, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(path, NS_NewSVGPathGeometryFrame),
+ SIMPLE_SVG_CREATE(defs, NS_NewSVGContainerFrame),
+ SIMPLE_SVG_CREATE(generic_, NS_NewSVGGenericContainerFrame),
+ { &nsGkAtoms::foreignObject,
+ FCDATA_WITH_WRAPPING_BLOCK(FCDATA_DISALLOW_OUT_OF_FLOW,
+ NS_NewSVGForeignObjectFrame,
+ nsCSSAnonBoxes::mozSVGForeignContent) },
+ SIMPLE_SVG_CREATE(a, NS_NewSVGAFrame),
+ SIMPLE_SVG_CREATE(linearGradient, NS_NewSVGLinearGradientFrame),
+ SIMPLE_SVG_CREATE(radialGradient, NS_NewSVGRadialGradientFrame),
+ SIMPLE_SVG_CREATE(stop, NS_NewSVGStopFrame),
+ SIMPLE_SVG_CREATE(use, NS_NewSVGUseFrame),
+ SIMPLE_SVG_CREATE(view, NS_NewSVGViewFrame),
+ SIMPLE_SVG_CREATE(image, NS_NewSVGImageFrame),
+ SIMPLE_SVG_CREATE(clipPath, NS_NewSVGClipPathFrame),
+ SIMPLE_SVG_CREATE(filter, NS_NewSVGFilterFrame),
+ SIMPLE_SVG_CREATE(pattern, NS_NewSVGPatternFrame),
+ SIMPLE_SVG_CREATE(mask, NS_NewSVGMaskFrame),
+ SIMPLE_SVG_CREATE(feDistantLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(fePointLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feSpotLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feBlend, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feColorMatrix, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feFuncR, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncG, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncB, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncA, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feComposite, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feComponentTransfer, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feConvolveMatrix, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feDiffuseLighting, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feDisplacementMap, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feDropShadow, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feFlood, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feGaussianBlur, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feImage, NS_NewSVGFEImageFrame),
+ SIMPLE_SVG_CREATE(feMerge, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feMergeNode, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feMorphology, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feOffset, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feSpecularLighting, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feTile, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feTurbulence, NS_NewSVGFELeafFrame)
+ };
+
+ const FrameConstructionData* data =
+ FindDataByTag(aTag, aElement, aStyleContext, sSVGData,
+ ArrayLength(sSVGData));
+
+ if (!data) {
+ data = &sContainerData;
+ }
+
+ return data;
+}
+
+void
+nsCSSFrameConstructor::AddPageBreakItem(nsIContent* aContent,
+ nsStyleContext* aMainStyleContext,
+ FrameConstructionItemList& aItems)
+{
+ // Use the same parent style context that |aMainStyleContext| has, since
+ // that's easier to re-resolve and it doesn't matter in practice.
+ // (Getting different parents can result in framechange hints, e.g.,
+ // for user-modify.)
+ RefPtr<nsStyleContext> pseudoStyle =
+ mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::pageBreak,
+ aMainStyleContext->GetParent());
+
+ MOZ_ASSERT(pseudoStyle->StyleDisplay()->mDisplay == StyleDisplay::Block,
+ "Unexpected display");
+
+ static const FrameConstructionData sPageBreakData =
+ FCDATA_DECL(FCDATA_SKIP_FRAMESET, NS_NewPageBreakFrame);
+
+ // Lie about the tag and namespace so we don't trigger anything
+ // interesting during frame construction.
+ aItems.AppendItem(&sPageBreakData, aContent, nsCSSAnonBoxes::pageBreak,
+ kNameSpaceID_None, nullptr, pseudoStyle.forget(),
+ true, nullptr);
+}
+
+bool
+nsCSSFrameConstructor::ShouldCreateItemsForChild(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame)
+{
+ aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ if (aContent->IsElement() && !aContent->IsStyledByServo()) {
+ // We can't just remove our pending restyle flags, since we may
+ // have restyle-later-siblings set on us. But we _can_ remove the
+ // "is possible restyle root" flags, and need to. Otherwise we can
+ // end up with stale such flags (e.g. if we used to have a
+ // display:none parent when our last restyle was posted and
+ // processed and now no longer do).
+ aContent->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS &
+ ~ELEMENT_PENDING_RESTYLE_FLAGS);
+ }
+
+ // XXX the GetContent() != aContent check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ if (aContent->GetPrimaryFrame() &&
+ aContent->GetPrimaryFrame()->GetContent() == aContent &&
+ !aState.mCreatingExtraFrames) {
+ NS_ERROR("asked to create frame construction item for a node that already "
+ "has a frame");
+ return false;
+ }
+
+ // don't create a whitespace frame if aParent doesn't want it
+ if (!NeedFrameFor(aState, aParentFrame, aContent)) {
+ return false;
+ }
+
+ // never create frames for comments or PIs
+ if (aContent->IsNodeOfType(nsINode::eCOMMENT) ||
+ aContent->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsCSSFrameConstructor::DoAddFrameConstructionItems(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsContainerFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren,
+ FrameConstructionItemList& aItems)
+{
+ uint32_t flags = ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK;
+ if (aParentFrame) {
+ if (aParentFrame->IsSVGText()) {
+ flags |= ITEM_IS_WITHIN_SVG_TEXT;
+ }
+ if (aParentFrame->GetType() == nsGkAtoms::blockFrame &&
+ aParentFrame->GetParent() &&
+ aParentFrame->GetParent()->GetType() == nsGkAtoms::svgTextFrame) {
+ flags |= ITEM_ALLOWS_TEXT_PATH_CHILD;
+ }
+ }
+ AddFrameConstructionItemsInternal(aState, aContent, aParentFrame,
+ aContent->NodeInfo()->NameAtom(),
+ aContent->GetNameSpaceID(),
+ aSuppressWhiteSpaceOptimizations,
+ aStyleContext,
+ flags, aAnonChildren,
+ aItems);
+}
+
+void
+nsCSSFrameConstructor::AddFrameConstructionItems(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ bool aSuppressWhiteSpaceOptimizations,
+ const InsertionPoint& aInsertion,
+ FrameConstructionItemList& aItems)
+{
+ nsContainerFrame* parentFrame = aInsertion.mParentFrame;
+ if (!ShouldCreateItemsForChild(aState, aContent, parentFrame)) {
+ return;
+ }
+ RefPtr<nsStyleContext> styleContext =
+ ResolveStyleContext(aInsertion, aContent, &aState);
+ DoAddFrameConstructionItems(aState, aContent, styleContext,
+ aSuppressWhiteSpaceOptimizations, parentFrame,
+ nullptr, aItems);
+}
+
+void
+nsCSSFrameConstructor::SetAsUndisplayedContent(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aList,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool aIsGeneratedContent)
+{
+ if (aStyleContext->GetPseudo()) {
+ if (aIsGeneratedContent) {
+ aContent->UnbindFromTree();
+ }
+ return;
+ }
+ NS_ASSERTION(!aIsGeneratedContent, "Should have had pseudo type");
+
+ if (aState.mCreatingExtraFrames) {
+ MOZ_ASSERT(GetUndisplayedContent(aContent),
+ "should have called SetUndisplayedContent earlier");
+ return;
+ }
+ aList.AppendUndisplayedItem(aContent, aStyleContext);
+}
+
+void
+nsCSSFrameConstructor::AddFrameConstructionItemsInternal(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsStyleContext* aStyleContext,
+ uint32_t aFlags,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren,
+ FrameConstructionItemList& aItems)
+{
+ NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT) ||
+ aContent->IsElement(),
+ "Shouldn't get anything else here!");
+ MOZ_ASSERT(!aContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
+ aContent->NodeInfo()->NameAtom() == nsGkAtoms::area);
+
+ // The following code allows the user to specify the base tag
+ // of an element using XBL. XUL and HTML objects (like boxes, menus, etc.)
+ // can then be extended arbitrarily.
+ const nsStyleDisplay* display = aStyleContext->StyleDisplay();
+ RefPtr<nsStyleContext> styleContext(aStyleContext);
+ PendingBinding* pendingBinding = nullptr;
+ if ((aFlags & ITEM_ALLOW_XBL_BASE) && display->mBinding)
+ {
+ // Ensure that our XBL bindings are installed.
+
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService)
+ return;
+
+ bool resolveStyle;
+
+ nsAutoPtr<PendingBinding> newPendingBinding(new PendingBinding());
+
+ nsresult rv = xblService->LoadBindings(aContent, display->mBinding->GetURI(),
+ display->mBinding->mOriginPrincipal,
+ getter_AddRefs(newPendingBinding->mBinding),
+ &resolveStyle);
+ if (NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED)
+ return;
+
+ if (newPendingBinding->mBinding) {
+ pendingBinding = newPendingBinding;
+ // aState takes over owning newPendingBinding
+ aState.AddPendingBinding(newPendingBinding.forget());
+ }
+
+ if (resolveStyle) {
+ styleContext =
+ ResolveStyleContext(styleContext->GetParent(), aContent, &aState);
+ display = styleContext->StyleDisplay();
+ aStyleContext = styleContext;
+ }
+
+ aTag = mDocument->BindingManager()->ResolveTag(aContent, &aNameSpaceID);
+ }
+
+ bool isGeneratedContent = ((aFlags & ITEM_IS_GENERATED_CONTENT) != 0);
+
+ // Pre-check for display "none" - if we find that, don't create
+ // any frame at all
+ if (StyleDisplay::None == display->mDisplay) {
+ SetAsUndisplayedContent(aState, aItems, aContent, styleContext, isGeneratedContent);
+ return;
+ }
+
+ bool isText = !aContent->IsElement();
+
+ // never create frames for non-option/optgroup kids of <select> and
+ // non-option kids of <optgroup> inside a <select>.
+ // XXXbz it's not clear how this should best work with XBL.
+ nsIContent *parent = aContent->GetParent();
+ if (parent) {
+ // Check tag first, since that check will usually fail
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::select, nsGkAtoms::optgroup) &&
+ // <option> is ok no matter what
+ !aContent->IsHTMLElement(nsGkAtoms::option) &&
+ // <optgroup> is OK in <select> but not in <optgroup>
+ (!aContent->IsHTMLElement(nsGkAtoms::optgroup) ||
+ !parent->IsHTMLElement(nsGkAtoms::select)) &&
+ // Allow native anonymous content no matter what
+ !aContent->IsRootOfNativeAnonymousSubtree()) {
+ // No frame for aContent
+ if (!isText) {
+ SetAsUndisplayedContent(aState, aItems, aContent, styleContext,
+ isGeneratedContent);
+ }
+ return;
+ }
+ }
+
+ // When constructing a child of a non-open <details>, create only the frame
+ // for the main <summary> element, and skip other elements. This only applies
+ // to things that are not roots of native anonymous subtrees (except for
+ // ::before and ::after); we always want to create "internal" anonymous
+ // content.
+ auto* details = HTMLDetailsElement::FromContentOrNull(parent);
+ if (details && details->IsDetailsEnabled() && !details->Open() &&
+ (!aContent->IsRootOfNativeAnonymousSubtree() ||
+ aContent->IsGeneratedContentContainerForBefore() ||
+ aContent->IsGeneratedContentContainerForAfter())) {
+ auto* summary = HTMLSummaryElement::FromContentOrNull(aContent);
+ if (!summary || !summary->IsMainSummary()) {
+ SetAsUndisplayedContent(aState, aItems, aContent, styleContext,
+ isGeneratedContent);
+ return;
+ }
+ }
+
+ bool isPopup = false;
+ // Try to find frame construction data for this content
+ const FrameConstructionData* data;
+ if (isText) {
+ data = FindTextData(aParentFrame);
+ if (!data) {
+ // Nothing to do here; suppressed text inside SVG
+ return;
+ }
+ } else {
+ Element* element = aContent->AsElement();
+
+ // Don't create frames for non-SVG element children of SVG elements.
+ if (aNameSpaceID != kNameSpaceID_SVG &&
+ ((aParentFrame &&
+ IsFrameForSVG(aParentFrame) &&
+ !aParentFrame->IsFrameOfType(nsIFrame::eSVGForeignObject)) ||
+ (aFlags & ITEM_IS_WITHIN_SVG_TEXT))) {
+ SetAsUndisplayedContent(aState, aItems, element, styleContext,
+ isGeneratedContent);
+ return;
+ }
+
+ data = FindHTMLData(element, aTag, aNameSpaceID, aParentFrame,
+ styleContext);
+ if (!data) {
+ data = FindXULTagData(element, aTag, aNameSpaceID, styleContext);
+ }
+ if (!data) {
+ data = FindMathMLData(element, aTag, aNameSpaceID, styleContext);
+ }
+ if (!data) {
+ data = FindSVGData(element, aTag, aNameSpaceID, aParentFrame,
+ aFlags & ITEM_IS_WITHIN_SVG_TEXT,
+ aFlags & ITEM_ALLOWS_TEXT_PATH_CHILD,
+ styleContext);
+ }
+
+ // Now check for XUL display types
+ if (!data) {
+ data = FindXULDisplayData(display, element, styleContext);
+ }
+
+ // And general display types
+ if (!data) {
+ data = FindDisplayData(display, element, styleContext);
+ }
+
+ NS_ASSERTION(data, "Should have frame construction data now");
+
+ if (data->mBits & FCDATA_SUPPRESS_FRAME) {
+ SetAsUndisplayedContent(aState, aItems, element, styleContext, isGeneratedContent);
+ return;
+ }
+
+#ifdef MOZ_XUL
+ if ((data->mBits & FCDATA_IS_POPUP) &&
+ (!aParentFrame || // Parent is inline
+ aParentFrame->GetType() != nsGkAtoms::menuFrame)) {
+ if (!aState.mPopupItems.containingBlock &&
+ !aState.mHavePendingPopupgroup) {
+ SetAsUndisplayedContent(aState, aItems, element, styleContext,
+ isGeneratedContent);
+ return;
+ }
+
+ isPopup = true;
+ }
+#endif /* MOZ_XUL */
+ }
+
+ uint32_t bits = data->mBits;
+
+ // Inside colgroups, suppress everything except columns.
+ if (aParentFrame &&
+ aParentFrame->GetType() == nsGkAtoms::tableColGroupFrame &&
+ (!(bits & FCDATA_IS_TABLE_PART) ||
+ display->mDisplay != StyleDisplay::TableColumn)) {
+ SetAsUndisplayedContent(aState, aItems, aContent, styleContext, isGeneratedContent);
+ return;
+ }
+
+ bool canHavePageBreak =
+ (aFlags & ITEM_ALLOW_PAGE_BREAK) &&
+ aState.mPresContext->IsPaginated() &&
+ !display->IsAbsolutelyPositionedStyle() &&
+ !(aParentFrame &&
+ aParentFrame->GetType() == nsGkAtoms::gridContainerFrame) &&
+ !(bits & FCDATA_IS_TABLE_PART) &&
+ !(bits & FCDATA_IS_SVG_TEXT);
+
+ if (canHavePageBreak && display->mBreakBefore) {
+ AddPageBreakItem(aContent, aStyleContext, aItems);
+ }
+
+ if (MOZ_UNLIKELY(bits & FCDATA_IS_CONTENTS)) {
+ if (!GetDisplayContentsStyleFor(aContent)) {
+ MOZ_ASSERT(styleContext->GetPseudo() || !isGeneratedContent,
+ "Should have had pseudo type");
+ aState.mFrameManager->SetDisplayContents(aContent, styleContext);
+ } else {
+ aState.mFrameManager->ChangeDisplayContents(aContent, styleContext);
+ }
+
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(aState.mTreeMatchContext);
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(aContent->AsElement());
+ } else {
+ ancestorPusher.PushStyleScope(aContent->AsElement());
+ }
+
+ if (aParentFrame) {
+ aParentFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT);
+ }
+ CreateGeneratedContentItem(aState, aParentFrame, aContent, styleContext,
+ CSSPseudoElementType::before, aItems);
+
+ FlattenedChildIterator iter(aContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (!ShouldCreateItemsForChild(aState, child, aParentFrame)) {
+ continue;
+ }
+
+ // Get the parent of the content and check if it is a XBL children element
+ // (if the content is a children element then parent != aContent because the
+ // FlattenedChildIterator will transitively iterate through <xbl:children>
+ // for default content). Push the children element as an ancestor here because
+ // it does not have a frame and would not otherwise be pushed as an ancestor.
+ nsIContent* parent = child->GetParent();
+ MOZ_ASSERT(parent, "Parent must be non-null because we are iterating children.");
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(aState.mTreeMatchContext);
+ if (parent != aContent && parent->IsElement()) {
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(parent->AsElement());
+ } else {
+ ancestorPusher.PushStyleScope(parent->AsElement());
+ }
+ }
+
+ RefPtr<nsStyleContext> childContext =
+ ResolveStyleContext(styleContext, child, &aState);
+ DoAddFrameConstructionItems(aState, child, childContext,
+ aSuppressWhiteSpaceOptimizations,
+ aParentFrame, aAnonChildren, aItems);
+ }
+ aItems.SetParentHasNoXBLChildren(!iter.XBLInvolved());
+
+ CreateGeneratedContentItem(aState, aParentFrame, aContent, styleContext,
+ CSSPseudoElementType::after, aItems);
+ if (canHavePageBreak && display->mBreakAfter) {
+ AddPageBreakItem(aContent, aStyleContext, aItems);
+ }
+ return;
+ }
+
+ FrameConstructionItem* item = nullptr;
+ if (details && details->IsDetailsEnabled() && details->Open()) {
+ auto* summary = HTMLSummaryElement::FromContentOrNull(aContent);
+ if (summary && summary->IsMainSummary()) {
+ // If details is open, the main summary needs to be rendered as if it is
+ // the first child, so add the item to the front of the item list.
+ item = aItems.PrependItem(data, aContent, aTag, aNameSpaceID,
+ pendingBinding, styleContext.forget(),
+ aSuppressWhiteSpaceOptimizations, aAnonChildren);
+ }
+ }
+
+ if (!item) {
+ item = aItems.AppendItem(data, aContent, aTag, aNameSpaceID,
+ pendingBinding, styleContext.forget(),
+ aSuppressWhiteSpaceOptimizations, aAnonChildren);
+ }
+ item->mIsText = isText;
+ item->mIsGeneratedContent = isGeneratedContent;
+ item->mIsAnonymousContentCreatorContent =
+ aFlags & ITEM_IS_ANONYMOUSCONTENTCREATOR_CONTENT;
+ if (isGeneratedContent) {
+ NS_ADDREF(item->mContent);
+ }
+ item->mIsRootPopupgroup =
+ aNameSpaceID == kNameSpaceID_XUL && aTag == nsGkAtoms::popupgroup &&
+ aContent->IsRootOfNativeAnonymousSubtree();
+ if (item->mIsRootPopupgroup) {
+ aState.mHavePendingPopupgroup = true;
+ }
+ item->mIsPopup = isPopup;
+ item->mIsForSVGAElement = aNameSpaceID == kNameSpaceID_SVG &&
+ aTag == nsGkAtoms::a;
+
+ if (canHavePageBreak && display->mBreakAfter) {
+ AddPageBreakItem(aContent, aStyleContext, aItems);
+ }
+
+ if (bits & FCDATA_IS_INLINE) {
+ // To correctly set item->mIsAllInline we need to build up our child items
+ // right now.
+ BuildInlineChildItems(aState, *item,
+ aFlags & ITEM_IS_WITHIN_SVG_TEXT,
+ aFlags & ITEM_ALLOWS_TEXT_PATH_CHILD);
+ item->mHasInlineEnds = true;
+ item->mIsBlock = false;
+ } else {
+ // Compute a boolean isInline which is guaranteed to be false for blocks
+ // (but may also be false for some inlines).
+ bool isInline =
+ // Table-internal things are inline-outside if and only if they're kids of
+ // inlines, since they'll trigger construction of inline-table
+ // pseudos.
+ ((bits & FCDATA_IS_TABLE_PART) &&
+ (!aParentFrame || // No aParentFrame means inline
+ aParentFrame->StyleDisplay()->mDisplay == StyleDisplay::Inline)) ||
+ // Things that are inline-outside but aren't inline frames are inline
+ display->IsInlineOutsideStyle() ||
+ // Popups that are certainly out of flow.
+ isPopup;
+
+ // Set mIsAllInline conservatively. It just might be that even an inline
+ // that has mIsAllInline false doesn't need an {ib} split. So this is just
+ // an optimization to keep from doing too much work in cases when we can
+ // show that mIsAllInline is true..
+ item->mIsAllInline = item->mHasInlineEnds = isInline ||
+ // Figure out whether we're guaranteed this item will be out of flow.
+ // This is not a precise test, since one of our ancestor inlines might add
+ // an absolute containing block (if it's relatively positioned) when there
+ // wasn't such a containing block before. But it's conservative in the
+ // sense that anything that will really end up as an in-flow non-inline
+ // will test false here. In other words, if this test is true we're
+ // guaranteed to be inline; if it's false we don't know what we'll end up
+ // as.
+ //
+ // If we make this test precise, we can remove some of the code dealing
+ // with the imprecision in ConstructInline and adjust the comments on
+ // mIsAllInline and mIsBlock in the header. And probably remove mIsBlock
+ // altogether, since then it will always be equal to !mHasInlineEnds.
+ (!(bits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
+ aState.GetGeometricParent(display, nullptr));
+
+ // Set mIsBlock conservatively. It's OK to set it false for some real
+ // blocks, but not OK to set it true for things that aren't blocks. Since
+ // isOutOfFlow might be false even in cases when the frame will end up
+ // out-of-flow, we can't use it here. But we _can_ say that the frame will
+ // for sure end up in-flow if it's not floated or absolutely positioned.
+ item->mIsBlock = !isInline &&
+ !display->IsAbsolutelyPositionedStyle() &&
+ !display->IsFloatingStyle() &&
+ !(bits & FCDATA_IS_SVG_TEXT);
+ }
+
+ if (item->mIsAllInline) {
+ aItems.InlineItemAdded();
+ } else if (item->mIsBlock) {
+ aItems.BlockItemAdded();
+ }
+
+ // Our item should be treated as a line participant if we have the relevant
+ // bit and are going to be in-flow. Note that this really only matters if
+ // our ancestor is a box or some such, so the fact that we might have an
+ // inline ancestor that might become a containing block is not relevant here.
+ if ((bits & FCDATA_IS_LINE_PARTICIPANT) &&
+ ((bits & FCDATA_DISALLOW_OUT_OF_FLOW) ||
+ !aState.GetGeometricParent(display, nullptr))) {
+ item->mIsLineParticipant = true;
+ aItems.LineParticipantItemAdded();
+ }
+}
+
+static void
+AddGenConPseudoToFrame(nsIFrame* aOwnerFrame, nsIContent* aContent)
+{
+ NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame),
+ "property should only be set on first continuation/ib-sibling");
+
+ FrameProperties props = aOwnerFrame->Properties();
+ nsIFrame::ContentArray* value = props.Get(nsIFrame::GenConProperty());
+ if (!value) {
+ value = new nsIFrame::ContentArray;
+ props.Set(nsIFrame::GenConProperty(), value);
+ }
+ value->AppendElement(aContent);
+}
+
+/**
+ * Return true if the frame construction item pointed to by aIter will
+ * create a frame adjacent to a line boundary in the frame tree, and that
+ * line boundary is induced by a content node adjacent to the frame's
+ * content node in the content tree. The latter condition is necessary so
+ * that ContentAppended/ContentInserted/ContentRemoved can easily find any
+ * text nodes that were suppressed here.
+ */
+bool
+nsCSSFrameConstructor::AtLineBoundary(FCItemIterator& aIter)
+{
+ if (aIter.item().mSuppressWhiteSpaceOptimizations) {
+ return false;
+ }
+
+ if (aIter.AtStart()) {
+ if (aIter.List()->HasLineBoundaryAtStart() &&
+ !aIter.item().mContent->GetPreviousSibling())
+ return true;
+ } else {
+ FCItemIterator prev = aIter;
+ prev.Prev();
+ if (prev.item().IsLineBoundary() &&
+ !prev.item().mSuppressWhiteSpaceOptimizations &&
+ aIter.item().mContent->GetPreviousSibling() == prev.item().mContent)
+ return true;
+ }
+
+ FCItemIterator next = aIter;
+ next.Next();
+ if (next.IsDone()) {
+ if (aIter.List()->HasLineBoundaryAtEnd() &&
+ !aIter.item().mContent->GetNextSibling())
+ return true;
+ } else {
+ if (next.item().IsLineBoundary() &&
+ !next.item().mSuppressWhiteSpaceOptimizations &&
+ aIter.item().mContent->GetNextSibling() == next.item().mContent)
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsCSSFrameConstructor::ConstructFramesFromItem(nsFrameConstructorState& aState,
+ FCItemIterator& aIter,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems)
+{
+ nsContainerFrame* adjParentFrame = aParentFrame;
+ FrameConstructionItem& item = aIter.item();
+ nsStyleContext* styleContext = item.mStyleContext;
+ AdjustParentFrame(&adjParentFrame, item.mFCData, styleContext);
+
+ if (item.mIsText) {
+ // If this is collapsible whitespace next to a line boundary,
+ // don't create a frame. item.IsWhitespace() also sets the
+ // NS_CREATE_FRAME_IF_NON_WHITESPACE flag in the text node. (If we
+ // end up creating a frame, nsTextFrame::Init will clear the flag.)
+ // We don't do this for generated content, because some generated
+ // text content is empty text nodes that are about to be initialized.
+ // (We check mAdditionalStateBits because only the generated content
+ // container's frame construction item is marked with
+ // mIsGeneratedContent, and we might not have an aParentFrame.)
+ // We don't do it for content that may have XBL anonymous siblings,
+ // because they make it difficult to correctly create the frame
+ // due to dynamic changes.
+ // We don't do it for SVG text, since we might need to position and
+ // measure the white space glyphs due to x/y/dx/dy attributes.
+ if (AtLineBoundary(aIter) &&
+ !styleContext->StyleText()->WhiteSpaceOrNewlineIsSignificant() &&
+ aIter.List()->ParentHasNoXBLChildren() &&
+ !(aState.mAdditionalStateBits & NS_FRAME_GENERATED_CONTENT) &&
+ (item.mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) &&
+ !(item.mFCData->mBits & FCDATA_IS_SVG_TEXT) &&
+ !mAlwaysCreateFramesForIgnorableWhitespace &&
+ item.IsWhitespace(aState))
+ return;
+
+ ConstructTextFrame(item.mFCData, aState, item.mContent,
+ adjParentFrame, styleContext,
+ aFrameItems);
+ return;
+ }
+
+ // Start background loads during frame construction so that we're
+ // guaranteed that they will be started before onload fires.
+ styleContext->StartBackgroundImageLoads();
+
+ nsFrameState savedStateBits = aState.mAdditionalStateBits;
+ if (item.mIsGeneratedContent) {
+ // Ensure that frames created here are all tagged with
+ // NS_FRAME_GENERATED_CONTENT.
+ aState.mAdditionalStateBits |= NS_FRAME_GENERATED_CONTENT;
+
+ // Note that we're not necessarily setting this property on the primary
+ // frame for the content for which this is generated content. We might be
+ // setting it on a table pseudo-frame inserted under that instead. That's
+ // OK, though; we just need to do the property set so that the content will
+ // get cleaned up when the frame is destroyed.
+ ::AddGenConPseudoToFrame(aParentFrame, item.mContent);
+
+ // Now that we've passed ownership of item.mContent to the frame, unset
+ // our generated content flag so we don't release or unbind it ourselves.
+ item.mIsGeneratedContent = false;
+ }
+
+ // XXXbz maybe just inline ConstructFrameFromItemInternal here or something?
+ ConstructFrameFromItemInternal(item, aState, adjParentFrame, aFrameItems);
+
+ aState.mAdditionalStateBits = savedStateBits;
+}
+
+
+inline bool
+IsRootBoxFrame(nsIFrame *aFrame)
+{
+ return (aFrame->GetType() == nsGkAtoms::rootFrame);
+}
+
+nsresult
+nsCSSFrameConstructor::ReconstructDocElementHierarchy()
+{
+ Element* rootElement = mDocument->GetRootElement();
+ if (!rootElement) {
+ /* nothing to do */
+ return NS_OK;
+ }
+ return RecreateFramesForContent(rootElement, false, REMOVE_FOR_RECONSTRUCTION,
+ nullptr);
+}
+
+nsContainerFrame*
+nsCSSFrameConstructor::GetAbsoluteContainingBlock(nsIFrame* aFrame,
+ ContainingBlockType aType)
+{
+ // Starting with aFrame, look for a frame that is absolutely positioned or
+ // relatively positioned (and transformed, if aType is FIXED)
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ if (frame->IsFrameOfType(nsIFrame::eMathML)) {
+ // If it's mathml, bail out -- no absolute positioning out from inside
+ // mathml frames. Note that we don't make this part of the loop
+ // condition because of the stuff at the end of this method...
+ return nullptr;
+ }
+
+ // Look for the ICB.
+ if (aType == FIXED_POS) {
+ nsIAtom* t = frame->GetType();
+ if (t == nsGkAtoms::viewportFrame ||
+ t == nsGkAtoms::pageContentFrame) {
+ return static_cast<nsContainerFrame*>(frame);
+ }
+ }
+
+ // If the frame is positioned, we will probably return it as the containing
+ // block (see the exceptions below). Otherwise, we'll start looking at the
+ // parent frame, unless we're dealing with a scrollframe.
+ // Scrollframes are special since they're not positioned, but their
+ // scrolledframe might be. So, we need to check this special case to return
+ // the correct containing block (the scrolledframe) in that case.
+ // If we're looking for a fixed-pos containing block and the frame is
+ // not transformed, skip it.
+ if (!frame->IsAbsPosContainingBlock() ||
+ (aType == FIXED_POS &&
+ !frame->IsFixedPosContainingBlock())) {
+ continue;
+ }
+ nsIFrame* absPosCBCandidate = frame;
+ nsIAtom* type = absPosCBCandidate->GetType();
+ if (type == nsGkAtoms::fieldSetFrame) {
+ absPosCBCandidate = static_cast<nsFieldSetFrame*>(absPosCBCandidate)->GetInner();
+ if (!absPosCBCandidate) {
+ continue;
+ }
+ type = absPosCBCandidate->GetType();
+ }
+ if (type == nsGkAtoms::scrollFrame) {
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(absPosCBCandidate);
+ absPosCBCandidate = scrollFrame->GetScrolledFrame();
+ if (!absPosCBCandidate) {
+ continue;
+ }
+ type = absPosCBCandidate->GetType();
+ }
+ // Only first continuations can be containing blocks.
+ absPosCBCandidate = absPosCBCandidate->FirstContinuation();
+ // Is the frame really an absolute container?
+ if (!absPosCBCandidate->IsAbsoluteContainer()) {
+ continue;
+ }
+
+ // For tables, skip the inner frame and consider the table wrapper frame.
+ if (type == nsGkAtoms::tableFrame) {
+ continue;
+ }
+ // For table wrapper frames, we can just return absPosCBCandidate.
+ MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(absPosCBCandidate),
+ "abs.pos. containing block must be nsContainerFrame sub-class");
+ return static_cast<nsContainerFrame*>(absPosCBCandidate);
+ }
+
+ MOZ_ASSERT(aType != FIXED_POS, "no ICB in this frame tree?");
+
+ // It is possible for the search for the containing block to fail, because
+ // no absolute container can be found in the parent chain. In those cases,
+ // we fall back to the document element's containing block.
+ return mHasRootAbsPosContainingBlock ? mDocElementContainingBlock : nullptr;
+}
+
+nsContainerFrame*
+nsCSSFrameConstructor::GetFloatContainingBlock(nsIFrame* aFrame)
+{
+ // Starting with aFrame, look for a frame that is a float containing block.
+ // IF we hit a mathml frame, bail out; we don't allow floating out of mathml
+ // frames, because they don't seem to be able to deal.
+ // The logic here needs to match the logic in ProcessChildren()
+ for (nsIFrame* containingBlock = aFrame;
+ containingBlock &&
+ !ShouldSuppressFloatingOfDescendants(containingBlock);
+ containingBlock = containingBlock->GetParent()) {
+ if (containingBlock->IsFloatContainingBlock()) {
+ MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(containingBlock),
+ "float containing block must be nsContainerFrame sub-class");
+ return static_cast<nsContainerFrame*>(containingBlock);
+ }
+ }
+
+ // If we didn't find a containing block, then there just isn't
+ // one.... return null
+ return nullptr;
+}
+
+/**
+ * This function will check whether aContainer has :after generated content.
+ * If so, appending to it should actually insert. The return value is the
+ * parent to use for newly-appended content. *aAfterFrame points to the :after
+ * frame before which appended content should go, if there is one.
+ */
+static nsContainerFrame*
+AdjustAppendParentForAfterContent(nsFrameManager* aFrameManager,
+ nsIContent* aContainer,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aChild,
+ nsIFrame** aAfterFrame)
+{
+ // If the parent frame has any pseudo-elements or aContainer is a
+ // display:contents node then we need to walk through the child
+ // frames to find the first one that is either a ::after frame for an
+ // ancestor of aChild or a frame that is for a node later in the
+ // document than aChild and return that in aAfterFrame.
+ if (aParentFrame->GetGenConPseudos() ||
+ nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(),
+ CSSPseudoElementType::after,
+ aParentFrame->PresContext()) ||
+ aFrameManager->GetDisplayContentsStyleFor(aContainer)) {
+ nsIFrame* afterFrame = nullptr;
+ nsContainerFrame* parent =
+ static_cast<nsContainerFrame*>(aParentFrame->LastContinuation());
+ bool done = false;
+ while (!done && parent) {
+ // Ensure that all normal flow children are on the principal child list.
+ parent->DrainSelfOverflowList();
+
+ nsIFrame* child = parent->GetChildList(nsIFrame::kPrincipalList).LastChild();
+ if (child && child->IsPseudoFrame(aContainer) &&
+ !child->IsGeneratedContentFrame()) {
+ // Drill down into non-generated pseudo frames of aContainer.
+ nsContainerFrame* childAsContainer = do_QueryFrame(child);
+ if (childAsContainer) {
+ parent = nsLayoutUtils::LastContinuationWithChild(childAsContainer);
+ continue;
+ }
+ }
+
+ for (; child; child = child->GetPrevSibling()) {
+ nsIContent* c = child->GetContent();
+ if (child->IsGeneratedContentFrame()) {
+ nsIContent* p = c->GetParent();
+ if (c->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) {
+ if (!nsContentUtils::ContentIsDescendantOf(aChild, p) &&
+ p != aContainer &&
+ nsContentUtils::PositionIsBefore(p, aChild)) {
+ // ::after generated content for content earlier in the doc and not
+ // for an ancestor. "p != aContainer" may seem redundant but it
+ // checks if the ::after belongs to the XBL insertion point we're
+ // inserting aChild into (in which case ContentIsDescendantOf is
+ // false even though p == aContainer).
+ // See layout/reftests/bugs/482592-1a.xhtml for an example of that.
+ done = true;
+ break;
+ }
+ } else if (nsContentUtils::PositionIsBefore(p, aChild)) {
+ // Non-::after generated content for content earlier in the doc.
+ done = true;
+ break;
+ }
+ } else if (nsContentUtils::PositionIsBefore(c, aChild)) {
+ // Content is before aChild.
+ done = true;
+ break;
+ }
+ afterFrame = child;
+ }
+
+ parent = static_cast<nsContainerFrame*>(parent->GetPrevContinuation());
+ }
+ if (afterFrame) {
+ *aAfterFrame = afterFrame;
+ return afterFrame->GetParent();
+ }
+ }
+
+ *aAfterFrame = nullptr;
+
+ if (IsFramePartOfIBSplit(aParentFrame)) {
+ // We might be in a situation where the last part of the {ib} split was
+ // empty. Since we have no ::after pseudo-element, we do in fact want to be
+ // appending to that last part, so advance to it if needed. Note that here
+ // aParentFrame is the result of a GetLastIBSplitSibling call, so must be
+ // either the last or next to last ib-split sibling.
+ nsContainerFrame* trailingInline = GetIBSplitSibling(aParentFrame);
+ if (trailingInline) {
+ aParentFrame = trailingInline;
+ }
+
+ // Always make sure to look at the last continuation of the frame
+ // for the {ib} case, even if that continuation is empty. We
+ // don't do this for the non-ib-split-frame case, since in the
+ // other cases appending to the last nonempty continuation is fine
+ // and in fact not doing that can confuse code that doesn't know
+ // to pull kids from continuations other than its next one.
+ aParentFrame =
+ static_cast<nsContainerFrame*>(aParentFrame->LastContinuation());
+ }
+
+ return aParentFrame;
+}
+
+/**
+ * This function will get the previous sibling to use for an append operation.
+ * it takes a parent frame (must not be null) and its :after frame (may be
+ * null).
+ */
+static nsIFrame*
+FindAppendPrevSibling(nsIFrame* aParentFrame, nsIFrame* aAfterFrame)
+{
+ if (aAfterFrame) {
+ NS_ASSERTION(aAfterFrame->GetParent() == aParentFrame, "Wrong parent");
+ NS_ASSERTION(aAfterFrame->GetPrevSibling() ||
+ aParentFrame->PrincipalChildList().FirstChild() == aAfterFrame,
+ ":after frame must be on the principal child list here");
+ return aAfterFrame->GetPrevSibling();
+ }
+
+ aParentFrame->DrainSelfOverflowList();
+
+ return aParentFrame->GetChildList(kPrincipalList).LastChild();
+}
+
+/**
+ * This function will get the next sibling for a frame insert operation given
+ * the parent and previous sibling. aPrevSibling may be null.
+ */
+static nsIFrame*
+GetInsertNextSibling(nsIFrame* aParentFrame, nsIFrame* aPrevSibling)
+{
+ if (aPrevSibling) {
+ return aPrevSibling->GetNextSibling();
+ }
+
+ return aParentFrame->PrincipalChildList().FirstChild();
+}
+
+/**
+ * This function is called by ContentAppended() and ContentInserted() when
+ * appending flowed frames to a parent's principal child list. It handles the
+ * case where the parent is the trailing inline of an {ib} split.
+ */
+nsresult
+nsCSSFrameConstructor::AppendFramesToParent(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameList,
+ nsIFrame* aPrevSibling,
+ bool aIsRecursiveCall)
+{
+ NS_PRECONDITION(!IsFramePartOfIBSplit(aParentFrame) ||
+ !GetIBSplitSibling(aParentFrame) ||
+ !GetIBSplitSibling(aParentFrame)->PrincipalChildList().FirstChild(),
+ "aParentFrame has a ib-split sibling with kids?");
+ NS_PRECONDITION(!aPrevSibling || aPrevSibling->GetParent() == aParentFrame,
+ "Parent and prevsibling don't match");
+
+ nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling);
+
+ NS_ASSERTION(nextSibling ||
+ !aParentFrame->GetNextContinuation() ||
+ !aParentFrame->GetNextContinuation()->PrincipalChildList().FirstChild() ||
+ aIsRecursiveCall,
+ "aParentFrame has later continuations with kids?");
+ NS_ASSERTION(nextSibling ||
+ !IsFramePartOfIBSplit(aParentFrame) ||
+ (IsInlineFrame(aParentFrame) &&
+ !GetIBSplitSibling(aParentFrame) &&
+ !aParentFrame->GetNextContinuation()) ||
+ aIsRecursiveCall,
+ "aParentFrame is not last?");
+
+ // If we're inserting a list of frames at the end of the trailing inline
+ // of an {ib} split, we may need to create additional {ib} siblings to parent
+ // them.
+ if (!nextSibling && IsFramePartOfIBSplit(aParentFrame)) {
+ // When we get here, our frame list might start with a block. If it does
+ // so, and aParentFrame is an inline, and it and all its previous
+ // continuations have no siblings, then put the initial blocks from the
+ // frame list into the previous block of the {ib} split. Note that we
+ // didn't want to stop at the block part of the split when figuring out
+ // initial parent, because that could screw up float parenting; it's easier
+ // to do this little fixup here instead.
+ if (aFrameList.NotEmpty() && !aFrameList.FirstChild()->IsInlineOutside()) {
+ // See whether our trailing inline is empty
+ nsIFrame* firstContinuation = aParentFrame->FirstContinuation();
+ if (firstContinuation->PrincipalChildList().IsEmpty()) {
+ // Our trailing inline is empty. Collect our starting blocks from
+ // aFrameList, get the right parent frame for them, and put them in.
+ nsFrameList::FrameLinkEnumerator firstNonBlockEnumerator =
+ FindFirstNonBlock(aFrameList);
+ nsFrameList blockKids = aFrameList.ExtractHead(firstNonBlockEnumerator);
+ NS_ASSERTION(blockKids.NotEmpty(), "No blocks?");
+
+ nsContainerFrame* prevBlock = GetIBSplitPrevSibling(firstContinuation);
+ prevBlock = static_cast<nsContainerFrame*>(prevBlock->LastContinuation());
+ NS_ASSERTION(prevBlock, "Should have previous block here");
+
+ MoveChildrenTo(aParentFrame, prevBlock, blockKids);
+ }
+ }
+
+ // We want to put some of the frames into this inline frame.
+ nsFrameList::FrameLinkEnumerator firstBlockEnumerator(aFrameList);
+ FindFirstBlock(firstBlockEnumerator);
+
+ nsFrameList inlineKids = aFrameList.ExtractHead(firstBlockEnumerator);
+ if (!inlineKids.IsEmpty()) {
+ AppendFrames(aParentFrame, kPrincipalList, inlineKids);
+ }
+
+ if (!aFrameList.IsEmpty()) {
+ bool positioned = aParentFrame->IsRelativelyPositioned();
+ nsFrameItems ibSiblings;
+ CreateIBSiblings(aState, aParentFrame, positioned, aFrameList,
+ ibSiblings);
+
+ // Make sure to trigger reflow of the inline that used to be our
+ // last one and now isn't anymore, since its GetSkipSides() has
+ // changed.
+ mPresShell->FrameNeedsReflow(aParentFrame,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Recurse so we create new ib siblings as needed for aParentFrame's parent
+ return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings,
+ aParentFrame, true);
+ }
+
+ return NS_OK;
+ }
+
+ // Insert the frames after our aPrevSibling
+ InsertFrames(aParentFrame, kPrincipalList, aPrevSibling, aFrameList);
+ return NS_OK;
+}
+
+#define UNSET_DISPLAY static_cast<StyleDisplay>(255)
+
+// This gets called to see if the frames corresponding to aSibling and aContent
+// should be siblings in the frame tree. Although (1) rows and cols, (2) row
+// groups and col groups, (3) row groups and captions, (4) legends and content
+// inside fieldsets, (5) popups and other kids of the menu are siblings from a
+// content perspective, they are not considered siblings in the frame tree.
+bool
+nsCSSFrameConstructor::IsValidSibling(nsIFrame* aSibling,
+ nsIContent* aContent,
+ StyleDisplay& aDisplay)
+{
+ nsIFrame* parentFrame = aSibling->GetParent();
+ nsIAtom* parentType = parentFrame->GetType();
+
+ StyleDisplay siblingDisplay = aSibling->GetDisplay();
+ if (StyleDisplay::TableColumnGroup == siblingDisplay ||
+ StyleDisplay::TableColumn == siblingDisplay ||
+ StyleDisplay::TableCaption == siblingDisplay ||
+ StyleDisplay::TableHeaderGroup == siblingDisplay ||
+ StyleDisplay::TableRowGroup == siblingDisplay ||
+ StyleDisplay::TableFooterGroup == siblingDisplay ||
+ nsGkAtoms::menuFrame == parentType) {
+ // if we haven't already, construct a style context to find the display type of aContent
+ if (UNSET_DISPLAY == aDisplay) {
+ nsIFrame* styleParent;
+ aSibling->GetParentStyleContext(&styleParent);
+ if (!styleParent) {
+ styleParent = aSibling->GetParent();
+ }
+ if (!styleParent) {
+ NS_NOTREACHED("Shouldn't happen");
+ return false;
+ }
+ if (aContent->IsNodeOfType(nsINode::eCOMMENT) ||
+ aContent->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
+ // Comments and processing instructions never have frames, so we
+ // should not try to generate style contexts for them.
+ return false;
+ }
+ // XXXbz when this code is killed, the state argument to
+ // ResolveStyleContext can be made non-optional.
+ RefPtr<nsStyleContext> styleContext =
+ ResolveStyleContext(styleParent, aContent, nullptr);
+ const nsStyleDisplay* display = styleContext->StyleDisplay();
+ aDisplay = display->mDisplay;
+ }
+ if (nsGkAtoms::menuFrame == parentType) {
+ return
+ (StyleDisplay::Popup == aDisplay) ==
+ (StyleDisplay::Popup == siblingDisplay);
+ }
+ // To have decent performance we want to return false in cases in which
+ // reordering the two siblings has no effect on display. To ensure
+ // correctness, we MUST return false in cases where the two siblings have
+ // the same desired parent type and live on different display lists.
+ // Specificaly, columns and column groups should only consider columns and
+ // column groups as valid siblings. Captions should only consider other
+ // captions. All other things should consider each other as valid
+ // siblings. The restriction in the |if| above on siblingDisplay is ok,
+ // because for correctness the only part that really needs to happen is to
+ // not consider captions, column groups, and row/header/footer groups
+ // siblings of each other. Treating a column or colgroup as a valid
+ // sibling of a non-table-related frame will just mean we end up reframing.
+ if ((siblingDisplay == StyleDisplay::TableCaption) !=
+ (aDisplay == StyleDisplay::TableCaption)) {
+ // One's a caption and the other is not. Not valid siblings.
+ return false;
+ }
+
+ if ((siblingDisplay == StyleDisplay::TableColumnGroup ||
+ siblingDisplay == StyleDisplay::TableColumn) !=
+ (aDisplay == StyleDisplay::TableColumnGroup ||
+ aDisplay == StyleDisplay::TableColumn)) {
+ // One's a column or column group and the other is not. Not valid
+ // siblings.
+ return false;
+ }
+ // Fall through; it's possible that the display type was overridden and
+ // a different sort of frame was constructed, so we may need to return false
+ // below.
+ }
+
+ if (IsFrameForFieldSet(parentFrame, parentType)) {
+ // Legends can be sibling of legends but not of other content in the fieldset
+ if (nsContainerFrame* cif = aSibling->GetContentInsertionFrame()) {
+ aSibling = cif;
+ }
+ nsIAtom* sibType = aSibling->GetType();
+ bool legendContent = aContent->IsHTMLElement(nsGkAtoms::legend);
+
+ if ((legendContent && (nsGkAtoms::legendFrame != sibType)) ||
+ (!legendContent && (nsGkAtoms::legendFrame == sibType)))
+ return false;
+ }
+
+ return true;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
+ nsIContent* aTargetContent,
+ StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame,
+ bool aPrevSibling)
+{
+ nsIFrame* sibling = aContent->GetPrimaryFrame();
+ if (!sibling && GetDisplayContentsStyleFor(aContent)) {
+ // A display:contents node - check if it has a ::before / ::after frame...
+ sibling = aPrevSibling ?
+ nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent) :
+ nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent);
+ if (!sibling) {
+ // ... then recurse into children ...
+ const bool forward = !aPrevSibling;
+ FlattenedChildIterator iter(aContent, forward);
+ sibling = aPrevSibling ?
+ FindPreviousSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame) :
+ FindNextSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame);
+ }
+ if (!sibling) {
+ // ... then ::after / ::before on the opposite end.
+ sibling = aPrevSibling ?
+ nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent) :
+ nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent);
+ }
+ if (!sibling) {
+ return nullptr;
+ }
+ } else if (!sibling || sibling->GetContent() != aContent) {
+ // XXX the GetContent() != aContent check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ return nullptr;
+ }
+
+ // If the frame is out-of-flow, GetPrimaryFrame() will have returned the
+ // out-of-flow frame; we want the placeholder.
+ if (sibling->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ nsIFrame* placeholderFrame = GetPlaceholderFrameFor(sibling);
+ NS_ASSERTION(placeholderFrame, "no placeholder for out-of-flow frame");
+ sibling = placeholderFrame;
+ }
+
+ // The frame we have now should never be a continuation
+ NS_ASSERTION(!sibling->GetPrevContinuation(), "How did that happen?");
+
+ if (aPrevSibling) {
+ // The frame may be a ib-split frame (a split inline frame that
+ // contains a block). Get the last part of that split.
+ if (IsFramePartOfIBSplit(sibling)) {
+ sibling = GetLastIBSplitSibling(sibling, true);
+ }
+
+ // The frame may have a continuation. If so, we want the last
+ // non-overflow-container continuation as our previous sibling.
+ sibling = sibling->GetTailContinuation();
+ }
+
+ if (aTargetContent &&
+ !IsValidSibling(sibling, aTargetContent, aTargetContentDisplay)) {
+ sibling = nullptr;
+ }
+
+ return sibling;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::FindPreviousSibling(FlattenedChildIterator aIter,
+ nsIContent* aTargetContent,
+ StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame)
+{
+ // Note: not all content objects are associated with a frame (e.g., if it's
+ // `display: none') so keep looking until we find a previous frame.
+ while (nsIContent* sibling = aIter.GetPreviousChild()) {
+ MOZ_ASSERT(sibling != aTargetContent);
+ nsIFrame* prevSibling =
+ FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay,
+ aParentFrame, true);
+ if (prevSibling) {
+ // Found a previous sibling, we're done!
+ return prevSibling;
+ }
+ }
+
+ return nullptr;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::FindNextSibling(FlattenedChildIterator aIter,
+ nsIContent* aTargetContent,
+ StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame)
+{
+ while (nsIContent* sibling = aIter.GetNextChild()) {
+ MOZ_ASSERT(sibling != aTargetContent);
+ nsIFrame* nextSibling =
+ FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay,
+ aParentFrame, false);
+
+ if (nextSibling) {
+ // We found a next sibling, we're done!
+ return nextSibling;
+ }
+ }
+
+ return nullptr;
+}
+
+// For fieldsets, returns the area frame, if the child is not a legend.
+static nsContainerFrame*
+GetAdjustedParentFrame(nsContainerFrame* aParentFrame,
+ nsIAtom* aParentFrameType,
+ nsIContent* aChildContent)
+{
+ NS_PRECONDITION(nsGkAtoms::tableWrapperFrame != aParentFrameType,
+ "Shouldn't be happening!");
+
+ nsContainerFrame* newParent = nullptr;
+
+ if (nsGkAtoms::fieldSetFrame == aParentFrameType) {
+ // If the parent is a fieldSet, use the fieldSet's area frame as the
+ // parent unless the new content is a legend.
+ if (!aChildContent->IsHTMLElement(nsGkAtoms::legend)) {
+ newParent = GetFieldSetBlockFrame(aParentFrame);
+ }
+ }
+ return newParent ? newParent : aParentFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::GetInsertionPrevSibling(InsertionPoint* aInsertion,
+ nsIContent* aChild,
+ bool* aIsAppend,
+ bool* aIsRangeInsertSafe,
+ nsIContent* aStartSkipChild,
+ nsIContent* aEndSkipChild)
+{
+ NS_PRECONDITION(aInsertion->mParentFrame, "Must have parent frame to start with");
+
+ *aIsAppend = false;
+
+ // Find the frame that precedes the insertion point. Walk backwards
+ // from the parent frame to get the parent content, because if an
+ // XBL insertion point is involved, we'll need to use _that_ to find
+ // the preceding frame.
+ FlattenedChildIterator iter(aInsertion->mContainer);
+ bool xblCase = iter.XBLInvolved() ||
+ aInsertion->mParentFrame->GetContent() != aInsertion->mContainer;
+ if (xblCase || !aChild->IsRootOfAnonymousSubtree()) {
+ // The check for IsRootOfAnonymousSubtree() is because editor is
+ // severely broken and calls us directly for native anonymous
+ // nodes that it creates.
+ if (aStartSkipChild) {
+ iter.Seek(aStartSkipChild);
+ } else {
+ iter.Seek(aChild);
+ }
+ } else {
+ // Prime the iterator for the call to FindPreviousSibling.
+ iter.GetNextChild();
+ MOZ_ASSERT(aChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+ "Someone passed native anonymous content directly into frame "
+ "construction. Stop doing that!");
+ }
+
+ // Note that FindPreviousSibling is passed the iterator by value, so that
+ // the later usage of the iterator starts from the same place.
+ StyleDisplay childDisplay = UNSET_DISPLAY;
+ nsIFrame* prevSibling =
+ FindPreviousSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame);
+
+ // Now, find the geometric parent so that we can handle
+ // continuations properly. Use the prev sibling if we have it;
+ // otherwise use the next sibling.
+ if (prevSibling) {
+ aInsertion->mParentFrame = prevSibling->GetParent()->GetContentInsertionFrame();
+ } else {
+ // If there is no previous sibling, then find the frame that follows
+ if (aEndSkipChild) {
+ iter.Seek(aEndSkipChild);
+ iter.GetPreviousChild();
+ }
+ nsIFrame* nextSibling =
+ FindNextSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame);
+ if (GetDisplayContentsStyleFor(aInsertion->mContainer)) {
+ if (!nextSibling) {
+ // Our siblings (if any) does not have a frame to guide us.
+ // The frame for aChild should be inserted whereever a frame for
+ // the container would be inserted. This is needed when inserting
+ // into nested display:contents nodes.
+ nsIContent* child = aInsertion->mContainer;
+ nsIContent* parent = child->GetParent();
+ aInsertion->mParentFrame =
+ ::GetAdjustedParentFrame(aInsertion->mParentFrame,
+ aInsertion->mParentFrame->GetType(),
+ parent);
+ InsertionPoint fakeInsertion(aInsertion->mParentFrame, parent);
+ nsIFrame* result = GetInsertionPrevSibling(&fakeInsertion, child, aIsAppend,
+ aIsRangeInsertSafe, nullptr, nullptr);
+ MOZ_ASSERT(aInsertion->mParentFrame->GetContent() ==
+ fakeInsertion.mParentFrame->GetContent());
+ // fakeInsertion.mParentFrame may now be a continuation of the frame
+ // we started with in the ctor above.
+ aInsertion->mParentFrame = fakeInsertion.mParentFrame;
+ return result;
+ }
+
+ prevSibling = nextSibling->GetPrevSibling();
+ }
+
+ if (nextSibling) {
+ aInsertion->mParentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
+ } else {
+ // No previous or next sibling, so treat this like an appended frame.
+ *aIsAppend = true;
+ if (IsFramePartOfIBSplit(aInsertion->mParentFrame)) {
+ // Since we're appending, we'll walk to the last anonymous frame
+ // that was created for the broken inline frame. But don't walk
+ // to the trailing inline if it's empty; stop at the block.
+ aInsertion->mParentFrame =
+ GetLastIBSplitSibling(aInsertion->mParentFrame, false);
+ }
+ // Get continuation that parents the last child. This MUST be done
+ // before the AdjustAppendParentForAfterContent call.
+ aInsertion->mParentFrame =
+ nsLayoutUtils::LastContinuationWithChild(aInsertion->mParentFrame);
+ // Deal with fieldsets
+ aInsertion->mParentFrame =
+ ::GetAdjustedParentFrame(aInsertion->mParentFrame,
+ aInsertion->mParentFrame->GetType(),
+ aChild);
+ nsIFrame* appendAfterFrame;
+ aInsertion->mParentFrame =
+ ::AdjustAppendParentForAfterContent(this, aInsertion->mContainer,
+ aInsertion->mParentFrame,
+ aChild, &appendAfterFrame);
+ prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, appendAfterFrame);
+ }
+ }
+
+ *aIsRangeInsertSafe = (childDisplay == UNSET_DISPLAY);
+ return prevSibling;
+}
+
+nsContainerFrame*
+nsCSSFrameConstructor::GetContentInsertionFrameFor(nsIContent* aContent)
+{
+ // Get the primary frame associated with the content
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+
+ if (!frame) {
+ if (GetDisplayContentsStyleFor(aContent)) {
+ nsIContent* parent = aContent->GetParent();
+ if (parent && parent == aContent->GetContainingShadow()) {
+ parent = parent->GetBindingParent();
+ }
+ frame = parent ? GetContentInsertionFrameFor(parent) : nullptr;
+ }
+ if (!frame) {
+ return nullptr;
+ }
+ } else {
+ // If the content of the frame is not the desired content then this is not
+ // really a frame for the desired content.
+ // XXX This check is needed due to bug 135040. Remove it once that's fixed.
+ if (frame->GetContent() != aContent) {
+ return nullptr;
+ }
+ }
+
+ nsContainerFrame* insertionFrame = frame->GetContentInsertionFrame();
+
+ NS_ASSERTION(!insertionFrame || insertionFrame == frame || !frame->IsLeaf(),
+ "The insertion frame is the primary frame or the primary frame isn't a leaf");
+
+ return insertionFrame;
+}
+
+static bool
+IsSpecialFramesetChild(nsIContent* aContent)
+{
+ // IMPORTANT: This must match the conditions in nsHTMLFramesetFrame::Init.
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame);
+}
+
+static void
+InvalidateCanvasIfNeeded(nsIPresShell* presShell, nsIContent* node);
+
+#ifdef MOZ_XUL
+
+static
+bool
+IsXULListBox(nsIContent* aContainer)
+{
+ return (aContainer->IsXULElement(nsGkAtoms::listbox));
+}
+
+static
+nsListBoxBodyFrame*
+MaybeGetListBoxBodyFrame(nsIContent* aContainer, nsIContent* aChild)
+{
+ if (!aContainer)
+ return nullptr;
+
+ if (IsXULListBox(aContainer) &&
+ aChild->IsXULElement(nsGkAtoms::listitem)) {
+ nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(aContainer);
+ nsCOMPtr<nsIBoxObject> boxObject;
+ xulElement->GetBoxObject(getter_AddRefs(boxObject));
+ nsCOMPtr<nsPIListBoxObject> listBoxObject = do_QueryInterface(boxObject);
+ if (listBoxObject) {
+ return listBoxObject->GetListBoxBody(false);
+ }
+ }
+
+ return nullptr;
+}
+#endif
+
+void
+nsCSSFrameConstructor::AddTextItemIfNeeded(nsFrameConstructorState& aState,
+ const InsertionPoint& aInsertion,
+ nsIContent* aPossibleTextContent,
+ FrameConstructionItemList& aItems)
+{
+ NS_PRECONDITION(aPossibleTextContent, "Must have node");
+ if (!aPossibleTextContent->IsNodeOfType(nsINode::eTEXT) ||
+ !aPossibleTextContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) {
+ // Not text, or not suppressed due to being all-whitespace (if it
+ // were being suppressed, it would have the
+ // NS_CREATE_FRAME_IF_NON_WHITESPACE flag)
+ return;
+ }
+ NS_ASSERTION(!aPossibleTextContent->GetPrimaryFrame(),
+ "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+ AddFrameConstructionItems(aState, aPossibleTextContent, false,
+ aInsertion, aItems);
+}
+
+void
+nsCSSFrameConstructor::ReframeTextIfNeeded(nsIContent* aParentContent,
+ nsIContent* aContent)
+{
+ if (!aContent->IsNodeOfType(nsINode::eTEXT) ||
+ !aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) {
+ // Not text, or not suppressed due to being all-whitespace (if it
+ // were being suppressed, it would have the
+ // NS_CREATE_FRAME_IF_NON_WHITESPACE flag)
+ return;
+ }
+ NS_ASSERTION(!aContent->GetPrimaryFrame(),
+ "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+ ContentInserted(aParentContent, aContent, nullptr, false);
+}
+
+// For inserts aChild should be valid, for appends it should be null.
+// Returns true if this operation can be lazy, false if not.
+bool
+nsCSSFrameConstructor::MaybeConstructLazily(Operation aOperation,
+ nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ if (mPresShell->GetPresContext()->IsChrome() || !aContainer ||
+ aContainer->IsInNativeAnonymousSubtree() || aContainer->IsXULElement()) {
+ return false;
+ }
+
+ if (aOperation == CONTENTINSERT) {
+ if (aChild->IsRootOfAnonymousSubtree() ||
+ (aChild->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
+ !aChild->IsInNativeAnonymousSubtree()) ||
+ aChild->IsEditable() || aChild->IsXULElement()) {
+ return false;
+ }
+ } else { // CONTENTAPPEND
+ NS_ASSERTION(aOperation == CONTENTAPPEND,
+ "operation should be either insert or append");
+ for (nsIContent* child = aChild; child; child = child->GetNextSibling()) {
+ NS_ASSERTION(!child->IsRootOfAnonymousSubtree(),
+ "Should be coming through the CONTENTAPPEND case");
+ if (child->IsXULElement() || child->IsEditable()) {
+ return false;
+ }
+ }
+ }
+
+ // We can construct lazily; just need to set suitable bits in the content
+ // tree.
+
+ // Walk up the tree setting the NODE_DESCENDANTS_NEED_FRAMES bit as we go.
+ nsIContent* content = aContainer;
+#ifdef DEBUG
+ // If we hit a node with no primary frame, or the NODE_NEEDS_FRAME bit set
+ // we want to assert, but leaf frames that process their own children and may
+ // ignore anonymous children (eg framesets) make this complicated. So we set
+ // these two booleans if we encounter these situations and unset them if we
+ // hit a node with a leaf frame.
+ bool noPrimaryFrame = false;
+ bool needsFrameBitSet = false;
+#endif
+ while (content &&
+ !content->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
+#ifdef DEBUG
+ if (content->GetPrimaryFrame() && content->GetPrimaryFrame()->IsLeaf()) {
+ noPrimaryFrame = needsFrameBitSet = false;
+ }
+ if (!noPrimaryFrame && !content->GetPrimaryFrame()) {
+ noPrimaryFrame = true;
+ }
+ if (!needsFrameBitSet && content->HasFlag(NODE_NEEDS_FRAME)) {
+ needsFrameBitSet = true;
+ }
+#endif
+ // XXXmats no lazy frames for display:contents descendants yet (bug 979782).
+ if (GetDisplayContentsStyleFor(content)) {
+ return false;
+ }
+ content->SetFlags(NODE_DESCENDANTS_NEED_FRAMES);
+ content = content->GetFlattenedTreeParent();
+ }
+#ifdef DEBUG
+ if (content && content->GetPrimaryFrame() &&
+ content->GetPrimaryFrame()->IsLeaf()) {
+ noPrimaryFrame = needsFrameBitSet = false;
+ }
+ NS_ASSERTION(!noPrimaryFrame, "Ancestors of nodes with frames to be "
+ "constructed lazily should have frames");
+ NS_ASSERTION(!needsFrameBitSet, "Ancestors of nodes with frames to be "
+ "constructed lazily should not have NEEDS_FRAME bit set");
+#endif
+
+ // Set NODE_NEEDS_FRAME on the new nodes.
+ if (aOperation == CONTENTINSERT) {
+ NS_ASSERTION(!aChild->GetPrimaryFrame() ||
+ aChild->GetPrimaryFrame()->GetContent() != aChild,
+ //XXX the aChild->GetPrimaryFrame()->GetContent() != aChild
+ // check is needed due to bug 135040. Remove it once that's
+ // fixed.
+ "setting NEEDS_FRAME on a node that already has a frame?");
+ aChild->SetFlags(NODE_NEEDS_FRAME);
+ } else { // CONTENTAPPEND
+ for (nsIContent* child = aChild; child; child = child->GetNextSibling()) {
+ NS_ASSERTION(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ //XXX the child->GetPrimaryFrame()->GetContent() != child
+ // check is needed due to bug 135040. Remove it once that's
+ // fixed.
+ "setting NEEDS_FRAME on a node that already has a frame?");
+ child->SetFlags(NODE_NEEDS_FRAME);
+ }
+ }
+
+ RestyleManager()->PostRestyleEventForLazyConstruction();
+ return true;
+}
+
+void
+nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent)
+{
+ NS_ASSERTION(!aContent->HasFlag(NODE_NEEDS_FRAME),
+ "shouldn't get here with a content node that has needs frame bit set");
+ NS_ASSERTION(aContent->HasFlag(NODE_DESCENDANTS_NEED_FRAMES),
+ "should only get here with a content node that has descendants needing frames");
+
+ aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
+
+ // We could either descend first (on nodes that don't have NODE_NEEDS_FRAME
+ // set) or issue content notifications for our kids first. In absence of
+ // anything definitive either way we'll go with the latter.
+
+ // It might be better to use GetChildArray and scan it completely first and
+ // then issue all notifications. (We have to scan it completely first because
+ // constructing frames can set attributes, which can change the storage of
+ // child lists).
+
+ // Scan the children of aContent to see what operations (if any) we need to
+ // perform.
+ uint32_t childCount = aContent->GetChildCount();
+ bool inRun = false;
+ nsIContent* firstChildInRun = nullptr;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsIContent* child = aContent->GetChildAt(i);
+ if (child->HasFlag(NODE_NEEDS_FRAME)) {
+ NS_ASSERTION(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ //XXX the child->GetPrimaryFrame()->GetContent() != child
+ // check is needed due to bug 135040. Remove it once that's
+ // fixed.
+ "NEEDS_FRAME set on a node that already has a frame?");
+ if (!inRun) {
+ inRun = true;
+ firstChildInRun = child;
+ }
+ } else {
+ if (inRun) {
+ inRun = false;
+ // generate a ContentRangeInserted for [startOfRun,i)
+ ContentRangeInserted(aContent, firstChildInRun, child, nullptr,
+ false);
+ }
+ }
+ }
+ if (inRun) {
+ ContentAppended(aContent, firstChildInRun, false);
+ }
+
+ // Now descend.
+ FlattenedChildIterator iter(aContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
+ CreateNeededFrames(child);
+ }
+ }
+}
+
+void nsCSSFrameConstructor::CreateNeededFrames()
+{
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+
+ Element* rootElement = mDocument->GetRootElement();
+ NS_ASSERTION(!rootElement || !rootElement->HasFlag(NODE_NEEDS_FRAME),
+ "root element should not have frame created lazily");
+ if (rootElement && rootElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
+ BeginUpdate();
+ CreateNeededFrames(rootElement);
+ EndUpdate();
+ }
+}
+
+void
+nsCSSFrameConstructor::IssueSingleInsertNofications(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ bool aAllowLazyConstruction)
+{
+ for (nsIContent* child = aStartChild;
+ child != aEndChild;
+ child = child->GetNextSibling()) {
+ if ((child->GetPrimaryFrame() || GetUndisplayedContent(child) ||
+ GetDisplayContentsStyleFor(child))
+#ifdef MOZ_XUL
+ // Except listboxes suck, so do NOT skip anything here if
+ // we plan to notify a listbox.
+ && !MaybeGetListBoxBodyFrame(aContainer, child)
+#endif
+ ) {
+ // Already have a frame or undisplayed entry for this content; a
+ // previous ContentInserted in this loop must have reconstructed
+ // its insertion parent. Skip it.
+ continue;
+ }
+ // Call ContentInserted with this node.
+ ContentInserted(aContainer, child, mTempFrameTreeState,
+ aAllowLazyConstruction);
+ }
+}
+
+nsCSSFrameConstructor::InsertionPoint
+nsCSSFrameConstructor::GetRangeInsertionPoint(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ bool aAllowLazyConstruction)
+{
+ // See if we have an XBL insertion point. If so, then that's our
+ // real parent frame; if not, then the frame hasn't been built yet
+ // and we just bail.
+ InsertionPoint insertionPoint = GetInsertionPoint(aContainer, nullptr);
+ if (!insertionPoint.mParentFrame && !insertionPoint.mMultiple) {
+ return insertionPoint; // Don't build the frames.
+ }
+
+ bool hasInsertion = false;
+ if (!insertionPoint.mMultiple) {
+ // XXXbz XBL2/sXBL issue
+ nsIDocument* document = aStartChild->GetComposedDoc();
+ // XXXbz how would |document| be null here?
+ if (document && aStartChild->GetXBLInsertionParent()) {
+ hasInsertion = true;
+ }
+ }
+
+ if (insertionPoint.mMultiple || hasInsertion) {
+ // We have an insertion point. There are some additional tests we need to do
+ // in order to ensure that an append is a safe operation.
+ uint32_t childCount = 0;
+
+ if (!insertionPoint.mMultiple) {
+ // We may need to make multiple ContentInserted calls instead. A
+ // reasonable heuristic to employ (in order to maintain good performance)
+ // is to find out if the insertion point's content node contains any
+ // explicit children. If it does not, then it is highly likely that
+ // an append is occurring. (Note it is not definite, and there are insane
+ // cases we will not deal with by employing this heuristic, but it beats
+ // always falling back to multiple ContentInserted calls).
+ //
+ // In the multiple insertion point case, we know we're going to need to do
+ // multiple ContentInserted calls anyway.
+ // XXXndeakin This test doesn't work in the new world. Or rather, it works, but
+ // it's slow
+ childCount = insertionPoint.mParentFrame->GetContent()->GetChildCount();
+ }
+
+ // If we have multiple insertion points or if we have an insertion point
+ // and the operation is not a true append or if the insertion point already
+ // has explicit children, then we must fall back.
+ if (insertionPoint.mMultiple || aEndChild != nullptr || childCount > 0) {
+ // Now comes the fun part. For each inserted child, make a
+ // ContentInserted call as if it had just gotten inserted and
+ // let ContentInserted handle the mess.
+ IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
+ aAllowLazyConstruction);
+ insertionPoint.mParentFrame = nullptr;
+ }
+ }
+
+ return insertionPoint;
+}
+
+bool
+nsCSSFrameConstructor::MaybeRecreateForFrameset(nsIFrame* aParentFrame,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild)
+{
+ if (aParentFrame->GetType() == nsGkAtoms::frameSetFrame) {
+ // Check whether we have any kids we care about.
+ for (nsIContent* cur = aStartChild;
+ cur != aEndChild;
+ cur = cur->GetNextSibling()) {
+ if (IsSpecialFramesetChild(cur)) {
+ // Just reframe the parent, since framesets are weird like that.
+ RecreateFramesForContent(aParentFrame->GetContent(), false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsresult
+nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ bool aAllowLazyConstruction)
+{
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+ NS_PRECONDITION(mUpdateCount != 0,
+ "Should be in an update while creating frames");
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentAppended container=%p "
+ "first-child=%p lazy=%d\n",
+ static_cast<void*>(aContainer), aFirstNewContent,
+ aAllowLazyConstruction);
+ if (gReallyNoisyContentUpdates && aContainer) {
+ aContainer->List(stdout, 0);
+ }
+ }
+#endif
+
+#ifdef DEBUG
+ for (nsIContent* child = aFirstNewContent;
+ child;
+ child = child->GetNextSibling()) {
+ // XXX the GetContent() != child check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ NS_ASSERTION(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ "asked to construct a frame for a node that already has a frame");
+ }
+#endif
+
+#ifdef MOZ_XUL
+ if (aContainer) {
+ int32_t namespaceID;
+ nsIAtom* tag =
+ mDocument->BindingManager()->ResolveTag(aContainer, &namespaceID);
+
+ // Just ignore tree tags, anyway we don't create any frames for them.
+ if (tag == nsGkAtoms::treechildren ||
+ tag == nsGkAtoms::treeitem ||
+ tag == nsGkAtoms::treerow)
+ return NS_OK;
+
+ }
+#endif // MOZ_XUL
+
+ if (aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
+ !aContainer->IsInNativeAnonymousSubtree() &&
+ !aFirstNewContent->IsInNativeAnonymousSubtree()) {
+ // Recreate frames if content is appended into a ShadowRoot
+ // because children of ShadowRoot are rendered in place of children
+ // of the host.
+ //XXXsmaug This is super unefficient!
+ nsIContent* bindingParent = aContainer->GetBindingParent();
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(bindingParent, false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // See comment in ContentRangeInserted for why this is necessary.
+ if (!GetContentInsertionFrameFor(aContainer) &&
+ !aContainer->IsActiveChildrenElement()) {
+ return NS_OK;
+ }
+
+ if (aAllowLazyConstruction &&
+ MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) {
+ return NS_OK;
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ InsertionPoint insertion =
+ GetRangeInsertionPoint(aContainer, aFirstNewContent, nullptr,
+ aAllowLazyConstruction);
+ nsContainerFrame*& parentFrame = insertion.mParentFrame;
+ LAYOUT_PHASE_TEMP_REENTER();
+ if (!parentFrame) {
+ return NS_OK;
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForFrameset(parentFrame, aFirstNewContent, nullptr)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ if (parentFrame->IsLeaf()) {
+ // Nothing to do here; we shouldn't be constructing kids of leaves
+ // Clear lazy bits so we don't try to construct again.
+ ClearLazyBits(aFirstNewContent, nullptr);
+ return NS_OK;
+ }
+
+ if (parentFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // If the frame we are manipulating is a ib-split frame (that is, one
+ // that's been created as a result of a block-in-inline situation) then we
+ // need to append to the last ib-split sibling, not to the frame itself.
+ bool parentIBSplit = IsFramePartOfIBSplit(parentFrame);
+ if (parentIBSplit) {
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentAppended: parentFrame=");
+ nsFrame::ListTag(stdout, parentFrame);
+ printf(" is ib-split\n");
+ }
+#endif
+
+ // Since we're appending, we'll walk to the last anonymous frame
+ // that was created for the broken inline frame. But don't walk
+ // to the trailing inline if it's empty; stop at the block.
+ parentFrame = GetLastIBSplitSibling(parentFrame, false);
+ }
+
+ // Get continuation that parents the last child. This MUST be done
+ // before the AdjustAppendParentForAfterContent call.
+ parentFrame = nsLayoutUtils::LastContinuationWithChild(parentFrame);
+
+ // We should never get here with fieldsets or details, since they have
+ // multiple insertion points.
+ MOZ_ASSERT(parentFrame->GetType() != nsGkAtoms::fieldSetFrame &&
+ parentFrame->GetType() != nsGkAtoms::detailsFrame,
+ "Parent frame should not be fieldset or details!");
+
+ // Deal with possible :after generated content on the parent
+ nsIFrame* parentAfterFrame;
+ parentFrame =
+ ::AdjustAppendParentForAfterContent(this, insertion.mContainer, parentFrame,
+ aFirstNewContent, &parentAfterFrame);
+
+ // Create some new frames
+ nsFrameConstructorState state(mPresShell,
+ GetAbsoluteContainingBlock(parentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(parentFrame, ABS_POS),
+ GetFloatContainingBlock(parentFrame));
+ state.mTreeMatchContext.InitAncestors(aContainer->AsElement());
+
+ // See if the containing block has :first-letter style applied.
+ bool haveFirstLetterStyle = false, haveFirstLineStyle = false;
+ nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
+ if (containingBlock) {
+ haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
+ haveFirstLineStyle =
+ ShouldHaveFirstLineStyle(containingBlock->GetContent(),
+ containingBlock->StyleContext());
+ }
+
+ if (haveFirstLetterStyle) {
+ // Before we get going, remove the current letter frames
+ RemoveLetterFrames(state.mPresShell, containingBlock);
+ }
+
+ nsIAtom* frameType = parentFrame->GetType();
+
+ FlattenedChildIterator iter(aContainer);
+ bool haveNoXBLChildren = (!iter.XBLInvolved() || !iter.GetNextChild());
+ FrameConstructionItemList items;
+ if (aFirstNewContent->GetPreviousSibling() &&
+ GetParentType(frameType) == eTypeBlock &&
+ haveNoXBLChildren) {
+ // If there's a text node in the normal content list just before the new
+ // items, and it has no frame, make a frame construction item for it. If it
+ // doesn't need a frame, ConstructFramesFromItemList below won't give it
+ // one. No need to do all this if our parent type is not block, though,
+ // since WipeContainingBlock already handles that situation.
+ //
+ // Because we're appending, we don't need to worry about any text
+ // after the appended content; there can only be XBL anonymous content
+ // (text in an XBL binding is not suppressed) or generated content
+ // (and bare text nodes are not generated). Native anonymous content
+ // generated by frames never participates in inline layout.
+ AddTextItemIfNeeded(state, insertion,
+ aFirstNewContent->GetPreviousSibling(), items);
+ }
+ for (nsIContent* child = aFirstNewContent;
+ child;
+ child = child->GetNextSibling()) {
+ AddFrameConstructionItems(state, child, false, insertion, items);
+ }
+
+ nsIFrame* prevSibling = ::FindAppendPrevSibling(parentFrame, parentAfterFrame);
+
+ // Perform special check for diddling around with the frames in
+ // a ib-split inline frame.
+ // If we're appending before :after content, then we're not really
+ // appending, so let WipeContainingBlock know that.
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeContainingBlock(state, containingBlock, parentFrame, items,
+ true, prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // If the parent is a block frame, and we're not in a special case
+ // where frames can be moved around, determine if the list is for the
+ // start or end of the block.
+ if (nsLayoutUtils::GetAsBlock(parentFrame) && !haveFirstLetterStyle &&
+ !haveFirstLineStyle && !parentIBSplit) {
+ items.SetLineBoundaryAtStart(!prevSibling ||
+ !prevSibling->IsInlineOutside() ||
+ prevSibling->GetType() == nsGkAtoms::brFrame);
+ // :after content can't be <br> so no need to check it
+ items.SetLineBoundaryAtEnd(!parentAfterFrame ||
+ !parentAfterFrame->IsInlineOutside());
+ }
+ // To suppress whitespace-only text frames, we have to verify that
+ // our container's DOM child list matches its flattened tree child list.
+ items.SetParentHasNoXBLChildren(haveNoXBLChildren);
+
+ nsFrameItems frameItems;
+ ConstructFramesFromItemList(state, items, parentFrame, frameItems);
+
+ for (nsIContent* child = aFirstNewContent;
+ child;
+ child = child->GetNextSibling()) {
+ // Invalidate now instead of before the WipeContainingBlock call, just in
+ // case we do wipe; in that case we don't need to do this walk at all.
+ // XXXbz does that matter? Would it make more sense to save some virtual
+ // GetChildAt calls instead and do this during construction of our
+ // FrameConstructionItemList?
+ InvalidateCanvasIfNeeded(mPresShell, child);
+ }
+
+ // If the container is a table and a caption was appended, it needs to be put
+ // in the table wrapper frame's additional child list.
+ nsFrameItems captionItems;
+ if (nsGkAtoms::tableFrame == frameType) {
+ // Pull out the captions. Note that we don't want to do that as we go,
+ // because processing a single caption can add a whole bunch of things to
+ // the frame items due to pseudoframe processing. So we'd have to pull
+ // captions from a list anyway; might as well do that here.
+ // XXXbz this is no longer true; we could pull captions directly out of the
+ // FrameConstructionItemList now.
+ PullOutCaptionFrames(frameItems, captionItems);
+ }
+
+ if (haveFirstLineStyle && parentFrame == containingBlock) {
+ // It's possible that some of the new frames go into a
+ // first-line frame. Look at them and see...
+ AppendFirstLineFrames(state, containingBlock->GetContent(),
+ containingBlock, frameItems);
+ }
+
+ // Notify the parent frame passing it the list of new frames
+ // Append the flowed frames to the principal child list; captions
+ // need special treatment
+ if (captionItems.NotEmpty()) { // append the caption to the table wrapper
+ NS_ASSERTION(nsGkAtoms::tableFrame == frameType, "how did that happen?");
+ nsContainerFrame* outerTable = parentFrame->GetParent();
+ AppendFrames(outerTable, nsIFrame::kCaptionList, captionItems);
+ }
+
+ if (frameItems.NotEmpty()) { // append the in-flow kids
+ AppendFramesToParent(state, parentFrame, frameItems, prevSibling);
+ }
+
+ // Recover first-letter frames
+ if (haveFirstLetterStyle) {
+ RecoverLetterFrames(containingBlock);
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentAppended: resulting frame model:\n");
+ parentFrame->List(stdout, 0);
+ }
+#endif
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->ContentRangeInserted(mPresShell, aContainer,
+ aFirstNewContent, nullptr);
+ }
+#endif
+
+ return NS_OK;
+}
+
+#ifdef MOZ_XUL
+
+enum content_operation
+{
+ CONTENT_INSERTED,
+ CONTENT_REMOVED
+};
+
+// Helper function to lookup the listbox body frame and send a notification
+// for insertion or removal of content
+static
+bool NotifyListBoxBody(nsPresContext* aPresContext,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ // Only used for the removed notification
+ nsIContent* aOldNextSibling,
+ nsIFrame* aChildFrame,
+ content_operation aOperation)
+{
+ nsListBoxBodyFrame* listBoxBodyFrame =
+ MaybeGetListBoxBodyFrame(aContainer, aChild);
+ if (listBoxBodyFrame) {
+ if (aOperation == CONTENT_REMOVED) {
+ // Except if we have an aChildFrame and its parent is not the right
+ // thing, then we don't do this. Pseudo frames are so much fun....
+ if (!aChildFrame || aChildFrame->GetParent() == listBoxBodyFrame) {
+ listBoxBodyFrame->OnContentRemoved(aPresContext, aContainer,
+ aChildFrame, aOldNextSibling);
+ return true;
+ }
+ } else {
+ listBoxBodyFrame->OnContentInserted(aChild);
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // MOZ_XUL
+
+nsresult
+nsCSSFrameConstructor::ContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsILayoutHistoryState* aFrameState,
+ bool aAllowLazyConstruction)
+{
+ return ContentRangeInserted(aContainer,
+ aChild,
+ aChild->GetNextSibling(),
+ aFrameState,
+ aAllowLazyConstruction);
+}
+
+// ContentRangeInserted handles creating frames for a range of nodes that
+// aren't at the end of their childlist. ContentRangeInserted isn't a real
+// content notification, but rather it handles regular ContentInserted calls
+// for a single node as well as the lazy construction of frames for a range of
+// nodes when called from CreateNeededFrames. For a range of nodes to be
+// suitable to have its frames constructed all at once they must meet the same
+// conditions that ContentAppended imposes (GetRangeInsertionPoint checks
+// these), plus more. Namely when finding the insertion prevsibling we must not
+// need to consult something specific to any one node in the range, so that the
+// insertion prevsibling would be the same for each node in the range. So we
+// pass the first node in the range to GetInsertionPrevSibling, and if
+// IsValidSibling (the only place GetInsertionPrevSibling might look at the
+// passed in node itself) needs to resolve style on the node we record this and
+// return that this range needs to be split up and inserted separately. Table
+// captions need extra attention as we need to determine where to insert them
+// in the caption list, while skipping any nodes in the range being inserted
+// (because when we treat the caption frames the other nodes have had their
+// frames constructed but not yet inserted into the frame tree).
+nsresult
+nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ nsILayoutHistoryState* aFrameState,
+ bool aAllowLazyConstruction)
+{
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+ NS_PRECONDITION(mUpdateCount != 0,
+ "Should be in an update while creating frames");
+
+ NS_PRECONDITION(aStartChild, "must always pass a child");
+
+ // XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and
+ // the :empty pseudo-class?
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentRangeInserted container=%p "
+ "start-child=%p end-child=%p lazy=%d\n",
+ static_cast<void*>(aContainer),
+ static_cast<void*>(aStartChild), static_cast<void*>(aEndChild),
+ aAllowLazyConstruction);
+ if (gReallyNoisyContentUpdates) {
+ if (aContainer) {
+ aContainer->List(stdout,0);
+ } else {
+ aStartChild->List(stdout, 0);
+ }
+ }
+ }
+#endif
+
+#ifdef DEBUG
+ for (nsIContent* child = aStartChild;
+ child != aEndChild;
+ child = child->GetNextSibling()) {
+ // XXX the GetContent() != child check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ NS_ASSERTION(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ "asked to construct a frame for a node that already has a frame");
+ }
+#endif
+
+ bool isSingleInsert = (aStartChild->GetNextSibling() == aEndChild);
+ NS_ASSERTION(isSingleInsert || !aAllowLazyConstruction,
+ "range insert shouldn't be lazy");
+ NS_ASSERTION(isSingleInsert || aEndChild,
+ "range should not include all nodes after aStartChild");
+
+#ifdef MOZ_XUL
+ if (aContainer && IsXULListBox(aContainer)) {
+ if (isSingleInsert) {
+ if (NotifyListBoxBody(mPresShell->GetPresContext(), aContainer,
+ // The insert case in NotifyListBoxBody
+ // doesn't use "old next sibling".
+ aStartChild, nullptr, nullptr, CONTENT_INSERTED)) {
+ return NS_OK;
+ }
+ } else {
+ // We don't handle a range insert to a listbox parent, issue single
+ // ContertInserted calls for each node inserted.
+ LAYOUT_PHASE_TEMP_EXIT();
+ IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
+ aAllowLazyConstruction);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ }
+#endif // MOZ_XUL
+
+ // If we have a null parent, then this must be the document element being
+ // inserted, or some other child of the document in the DOM (might be a PI,
+ // say).
+ if (! aContainer) {
+ NS_ASSERTION(isSingleInsert,
+ "root node insertion should be a single insertion");
+ Element *docElement = mDocument->GetRootElement();
+
+ if (aStartChild != docElement) {
+ // Not the root element; just bail out
+ return NS_OK;
+ }
+
+ NS_PRECONDITION(nullptr == mRootElementFrame,
+ "root element frame already created");
+
+ // Create frames for the document element and its child elements
+ nsIFrame* docElementFrame =
+ ConstructDocElementFrame(docElement, aFrameState);
+
+ if (docElementFrame) {
+ InvalidateCanvasIfNeeded(mPresShell, aStartChild);
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentRangeInserted: resulting frame "
+ "model:\n");
+ docElementFrame->List(stdout, 0);
+ }
+#endif
+ }
+
+ if (aFrameState) {
+ // Restore frame state for the root scroll frame if there is one
+ nsIFrame* rootScrollFrame = mPresShell->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ RestoreFrameStateFor(rootScrollFrame, aFrameState);
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->ContentRangeInserted(mPresShell, aContainer,
+ aStartChild, aEndChild);
+ }
+#endif
+
+ return NS_OK;
+ }
+
+ if (aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
+ !aContainer->IsInNativeAnonymousSubtree() &&
+ (!aStartChild || !aStartChild->IsInNativeAnonymousSubtree()) &&
+ (!aEndChild || !aEndChild->IsInNativeAnonymousSubtree())) {
+ // Recreate frames if content is inserted into a ShadowRoot
+ // because children of ShadowRoot are rendered in place of
+ // the children of the host.
+ //XXXsmaug This is super unefficient!
+ nsIContent* bindingParent = aContainer->GetBindingParent();
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(bindingParent, false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // Put 'parentFrame' inside a scope so we don't confuse it with
+ // 'insertion.mParentFrame' later.
+ {
+ nsContainerFrame* parentFrame = GetContentInsertionFrameFor(aContainer);
+ // The xbl:children element won't have a frame, but default content can have the children as
+ // a parent. While its uncommon to change the structure of the default content itself, a label,
+ // for example, can be reframed by having its value attribute set or removed.
+ if (!parentFrame && !aContainer->IsActiveChildrenElement()) {
+ return NS_OK;
+ }
+
+ // Otherwise, we've got parent content. Find its frame.
+ NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer ||
+ GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!");
+
+ if (aAllowLazyConstruction &&
+ MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) {
+ return NS_OK;
+ }
+ }
+
+ InsertionPoint insertion;
+ if (isSingleInsert) {
+ // See if we have an XBL insertion point. If so, then that's our
+ // real parent frame; if not, then the frame hasn't been built yet
+ // and we just bail.
+ insertion = GetInsertionPoint(aContainer, aStartChild);
+ } else {
+ // Get our insertion point. If we need to issue single ContentInserted's
+ // GetRangeInsertionPoint will take care of that for us.
+ LAYOUT_PHASE_TEMP_EXIT();
+ insertion = GetRangeInsertionPoint(aContainer, aStartChild, aEndChild,
+ aAllowLazyConstruction);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+
+ if (!insertion.mParentFrame) {
+ return NS_OK;
+ }
+
+ bool isAppend, isRangeInsertSafe;
+ nsIFrame* prevSibling = GetInsertionPrevSibling(&insertion, aStartChild,
+ &isAppend, &isRangeInsertSafe);
+
+ // check if range insert is safe
+ if (!isSingleInsert && !isRangeInsertSafe) {
+ // must fall back to a single ContertInserted for each child in the range
+ LAYOUT_PHASE_TEMP_EXIT();
+ IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
+ aAllowLazyConstruction);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+
+ nsIContent* container = insertion.mParentFrame->GetContent();
+
+ nsIAtom* frameType = insertion.mParentFrame->GetType();
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForFrameset(insertion.mParentFrame, aStartChild, aEndChild)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // We should only get here with fieldsets when doing a single insert, because
+ // fieldsets have multiple insertion points.
+ NS_ASSERTION(isSingleInsert || frameType != nsGkAtoms::fieldSetFrame,
+ "Unexpected parent");
+ if (IsFrameForFieldSet(insertion.mParentFrame, frameType) &&
+ aStartChild->NodeInfo()->NameAtom() == nsGkAtoms::legend) {
+ // Just reframe the parent, since figuring out whether this
+ // should be the new legend and then handling it is too complex.
+ // We could do a little better here --- check if the fieldset already
+ // has a legend which occurs earlier in its child list than this node,
+ // and if so, proceed. But we'd have to extend nsFieldSetFrame
+ // to locate this legend in the inserted frames and extract it.
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // We should only get here with details when doing a single insertion because
+ // we treat details frame as if it has multiple insertion points.
+ MOZ_ASSERT(isSingleInsert || frameType != nsGkAtoms::detailsFrame);
+ if (frameType == nsGkAtoms::detailsFrame) {
+ // When inserting an element into <details>, just reframe the details frame
+ // and let it figure out where the element should be laid out. It might seem
+ // expensive to recreate the entire details frame, but it's the simplest way
+ // to handle the insertion.
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv =
+ RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // Don't construct kids of leaves
+ if (insertion.mParentFrame->IsLeaf()) {
+ // Clear lazy bits so we don't try to construct again.
+ ClearLazyBits(aStartChild, aEndChild);
+ return NS_OK;
+ }
+
+ if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ nsFrameConstructorState state(mPresShell,
+ GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
+ GetFloatContainingBlock(insertion.mParentFrame),
+ do_AddRef(aFrameState));
+ state.mTreeMatchContext.InitAncestors(aContainer ?
+ aContainer->AsElement() :
+ nullptr);
+
+ // Recover state for the containing block - we need to know if
+ // it has :first-letter or :first-line style applied to it. The
+ // reason we care is that the internal structure in these cases
+ // is not the normal structure and requires custom updating
+ // logic.
+ nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
+ bool haveFirstLetterStyle = false;
+ bool haveFirstLineStyle = false;
+
+ // In order to shave off some cycles, we only dig up the
+ // containing block haveFirst* flags if the parent frame where
+ // the insertion/append is occurring is an inline or block
+ // container. For other types of containers this isn't relevant.
+ StyleDisplay parentDisplay = insertion.mParentFrame->GetDisplay();
+
+ // Examine the insertion.mParentFrame where the insertion is taking
+ // place. If it's a certain kind of container then some special
+ // processing is done.
+ if ((StyleDisplay::Block == parentDisplay) ||
+ (StyleDisplay::ListItem == parentDisplay) ||
+ (StyleDisplay::Inline == parentDisplay) ||
+ (StyleDisplay::InlineBlock == parentDisplay)) {
+ // Recover the special style flags for the containing block
+ if (containingBlock) {
+ haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
+ haveFirstLineStyle =
+ ShouldHaveFirstLineStyle(containingBlock->GetContent(),
+ containingBlock->StyleContext());
+ }
+
+ if (haveFirstLetterStyle) {
+ // If our current insertion.mParentFrame is a Letter frame, use its parent as our
+ // new parent hint
+ if (insertion.mParentFrame->GetType() == nsGkAtoms::letterFrame) {
+ // If insertion.mParentFrame is out of flow, then we actually want the parent of
+ // the placeholder frame.
+ if (insertion.mParentFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ nsPlaceholderFrame* placeholderFrame =
+ GetPlaceholderFrameFor(insertion.mParentFrame);
+ NS_ASSERTION(placeholderFrame, "No placeholder for out-of-flow?");
+ insertion.mParentFrame = placeholderFrame->GetParent();
+ } else {
+ insertion.mParentFrame = insertion.mParentFrame->GetParent();
+ }
+ }
+
+ // Remove the old letter frames before doing the insertion
+ RemoveLetterFrames(mPresShell, state.mFloatedItems.containingBlock);
+
+ // Removing the letterframes messes around with the frame tree, removing
+ // and creating frames. We need to reget our prevsibling, parent frame,
+ // etc.
+ prevSibling = GetInsertionPrevSibling(&insertion, aStartChild, &isAppend,
+ &isRangeInsertSafe);
+
+ // Need check whether a range insert is still safe.
+ if (!isSingleInsert && !isRangeInsertSafe) {
+ // Need to recover the letter frames first.
+ RecoverLetterFrames(state.mFloatedItems.containingBlock);
+
+ // must fall back to a single ContertInserted for each child in the range
+ LAYOUT_PHASE_TEMP_EXIT();
+ IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
+ aAllowLazyConstruction);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+
+ container = insertion.mParentFrame->GetContent();
+ frameType = insertion.mParentFrame->GetType();
+ }
+ }
+
+ if (!prevSibling) {
+ // We're inserting the new frames as the first child. See if the
+ // parent has a :before pseudo-element
+ nsIFrame* firstChild = insertion.mParentFrame->PrincipalChildList().FirstChild();
+
+ if (firstChild &&
+ nsLayoutUtils::IsGeneratedContentFor(container, firstChild,
+ nsCSSPseudoElements::before)) {
+ // Insert the new frames after the last continuation of the :before
+ prevSibling = firstChild->GetTailContinuation();
+ insertion.mParentFrame = prevSibling->GetParent()->GetContentInsertionFrame();
+ // Don't change isAppend here; we'll can call AppendFrames as needed, and
+ // the change to our prevSibling doesn't affect that.
+ }
+ }
+
+ FrameConstructionItemList items;
+ ParentType parentType = GetParentType(frameType);
+ FlattenedChildIterator iter(aContainer);
+ bool haveNoXBLChildren = (!iter.XBLInvolved() || !iter.GetNextChild());
+ if (aStartChild->GetPreviousSibling() &&
+ parentType == eTypeBlock && haveNoXBLChildren) {
+ // If there's a text node in the normal content list just before the
+ // new nodes, and it has no frame, make a frame construction item for
+ // it, because it might need a frame now. No need to do this if our
+ // parent type is not block, though, since WipeContainingBlock
+ // already handles that sitation.
+ AddTextItemIfNeeded(state, insertion, aStartChild->GetPreviousSibling(),
+ items);
+ }
+
+ if (isSingleInsert) {
+ AddFrameConstructionItems(state, aStartChild,
+ aStartChild->IsRootOfAnonymousSubtree(),
+ insertion, items);
+ } else {
+ for (nsIContent* child = aStartChild;
+ child != aEndChild;
+ child = child->GetNextSibling()){
+ AddFrameConstructionItems(state, child, false, insertion, items);
+ }
+ }
+
+ if (aEndChild && parentType == eTypeBlock && haveNoXBLChildren) {
+ // If there's a text node in the normal content list just after the
+ // new nodes, and it has no frame, make a frame construction item for
+ // it, because it might need a frame now. No need to do this if our
+ // parent type is not block, though, since WipeContainingBlock
+ // already handles that sitation.
+ AddTextItemIfNeeded(state, insertion, aEndChild, items);
+ }
+
+ // Perform special check for diddling around with the frames in
+ // a special inline frame.
+ // If we're appending before :after content, then we're not really
+ // appending, so let WipeContainingBlock know that.
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeContainingBlock(state, containingBlock, insertion.mParentFrame, items,
+ isAppend, prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // If the container is a table and a caption will be appended, it needs to be
+ // put in the table wrapper frame's additional child list.
+ // We make no attempt here to set flags to indicate whether the list
+ // will be at the start or end of a block. It doesn't seem worthwhile.
+ nsFrameItems frameItems, captionItems;
+ ConstructFramesFromItemList(state, items, insertion.mParentFrame, frameItems);
+
+ if (frameItems.NotEmpty()) {
+ for (nsIContent* child = aStartChild;
+ child != aEndChild;
+ child = child->GetNextSibling()){
+ InvalidateCanvasIfNeeded(mPresShell, child);
+ }
+
+ if (nsGkAtoms::tableFrame == frameType ||
+ nsGkAtoms::tableWrapperFrame == frameType) {
+ PullOutCaptionFrames(frameItems, captionItems);
+ }
+ }
+
+ // If the parent of our current prevSibling is different from the frame we'll
+ // actually use as the parent, then the calculated insertion point is now
+ // invalid and as it is unknown where to insert correctly we append instead
+ // (bug 341858).
+ // This can affect our prevSibling and isAppend, but should not have any
+ // effect on the WipeContainingBlock above, since this should only happen
+ // when neither parent is a ib-split frame and should not affect whitespace
+ // handling inside table-related frames (and in fact, can only happen when
+ // one of the parents is a table wrapper and one is an inner table or when the
+ // parent is a fieldset or fieldset content frame). So it won't affect the
+ // {ib} or XUL box cases in WipeContainingBlock(), and the table pseudo
+ // handling will only be affected by us maybe thinking we're not inserting
+ // at the beginning, whereas we really are. That would have made us reframe
+ // unnecessarily, but that's ok.
+ // XXXbz we should push our frame construction item code up higher, so we
+ // know what our items are by the time we start figuring out previous
+ // siblings
+ if (prevSibling && frameItems.NotEmpty() &&
+ frameItems.FirstChild()->GetParent() != prevSibling->GetParent()) {
+#ifdef DEBUG
+ nsIFrame* frame1 = frameItems.FirstChild()->GetParent();
+ nsIFrame* frame2 = prevSibling->GetParent();
+ NS_ASSERTION(!IsFramePartOfIBSplit(frame1) &&
+ !IsFramePartOfIBSplit(frame2),
+ "Neither should be ib-split");
+ NS_ASSERTION((frame1->GetType() == nsGkAtoms::tableFrame &&
+ frame2->GetType() == nsGkAtoms::tableWrapperFrame) ||
+ (frame1->GetType() == nsGkAtoms::tableWrapperFrame &&
+ frame2->GetType() == nsGkAtoms::tableFrame) ||
+ frame1->GetType() == nsGkAtoms::fieldSetFrame ||
+ (frame1->GetParent() &&
+ frame1->GetParent()->GetType() == nsGkAtoms::fieldSetFrame),
+ "Unexpected frame types");
+#endif
+ isAppend = true;
+ nsIFrame* appendAfterFrame;
+ insertion.mParentFrame =
+ ::AdjustAppendParentForAfterContent(this, container,
+ frameItems.FirstChild()->GetParent(),
+ aStartChild, &appendAfterFrame);
+ prevSibling = ::FindAppendPrevSibling(insertion.mParentFrame, appendAfterFrame);
+ }
+
+ if (haveFirstLineStyle && insertion.mParentFrame == containingBlock) {
+ // It's possible that the new frame goes into a first-line
+ // frame. Look at it and see...
+ if (isAppend) {
+ // Use append logic when appending
+ AppendFirstLineFrames(state, containingBlock->GetContent(),
+ containingBlock, frameItems);
+ }
+ else {
+ // Use more complicated insert logic when inserting
+ // XXXbz this method is a no-op, so it's easy for the args being passed
+ // here to make no sense without anyone noticing... If it ever stops
+ // being a no-op, vet them carefully!
+ InsertFirstLineFrames(state, container, containingBlock, &insertion.mParentFrame,
+ prevSibling, frameItems);
+ }
+ }
+
+ // We might have captions; put them into the caption list of the
+ // table wrapper frame.
+ if (captionItems.NotEmpty()) {
+ NS_ASSERTION(nsGkAtoms::tableFrame == frameType ||
+ nsGkAtoms::tableWrapperFrame == frameType,
+ "parent for caption is not table?");
+ // We need to determine where to put the caption items; start with the
+ // the parent frame that has already been determined and get the insertion
+ // prevsibling of the first caption item.
+ bool captionIsAppend;
+ nsIFrame* captionPrevSibling = nullptr;
+
+ // aIsRangeInsertSafe is ignored on purpose because it is irrelevant here.
+ bool ignored;
+ InsertionPoint captionInsertion(insertion.mParentFrame, insertion.mContainer);
+ if (isSingleInsert) {
+ captionPrevSibling =
+ GetInsertionPrevSibling(&captionInsertion, aStartChild,
+ &captionIsAppend, &ignored);
+ } else {
+ nsIContent* firstCaption = captionItems.FirstChild()->GetContent();
+ // It is very important here that we skip the children in
+ // [aStartChild,aEndChild) when looking for a
+ // prevsibling.
+ captionPrevSibling =
+ GetInsertionPrevSibling(&captionInsertion, firstCaption,
+ &captionIsAppend, &ignored,
+ aStartChild, aEndChild);
+ }
+
+ nsContainerFrame* outerTable = nullptr;
+ if (GetCaptionAdjustedParent(captionInsertion.mParentFrame,
+ captionItems.FirstChild(),
+ &outerTable)) {
+ // If the parent is not a table wrapper frame we will try to add frames
+ // to a named child list that the parent does not honor and the frames
+ // will get lost.
+ NS_ASSERTION(nsGkAtoms::tableWrapperFrame == outerTable->GetType(),
+ "Pseudo frame construction failure; "
+ "a caption can be only a child of a table wrapper frame");
+
+ // If the parent of our current prevSibling is different from the frame
+ // we'll actually use as the parent, then the calculated insertion
+ // point is now invalid (bug 341382).
+ if (captionPrevSibling &&
+ captionPrevSibling->GetParent() != outerTable) {
+ captionPrevSibling = nullptr;
+ }
+ if (captionIsAppend) {
+ AppendFrames(outerTable, nsIFrame::kCaptionList, captionItems);
+ } else {
+ InsertFrames(outerTable, nsIFrame::kCaptionList,
+ captionPrevSibling, captionItems);
+ }
+ }
+ }
+
+ if (frameItems.NotEmpty()) {
+ // Notify the parent frame
+ if (isAppend) {
+ AppendFramesToParent(state, insertion.mParentFrame, frameItems, prevSibling);
+ } else {
+ InsertFrames(insertion.mParentFrame, kPrincipalList, prevSibling, frameItems);
+ }
+ }
+
+ if (haveFirstLetterStyle) {
+ // Recover the letter frames for the containing block when
+ // it has first-letter style.
+ RecoverLetterFrames(state.mFloatedItems.containingBlock);
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates && insertion.mParentFrame) {
+ printf("nsCSSFrameConstructor::ContentRangeInserted: resulting frame model:\n");
+ insertion.mParentFrame->List(stdout, 0);
+ }
+#endif
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->ContentRangeInserted(mPresShell, aContainer,
+ aStartChild, aEndChild);
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aOldNextSibling,
+ RemoveFlags aFlags,
+ bool* aDidReconstruct,
+ nsIContent** aDestroyedFramesFor)
+{
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+ NS_PRECONDITION(mUpdateCount != 0,
+ "Should be in an update while destroying frames");
+
+ *aDidReconstruct = false;
+ if (aDestroyedFramesFor) {
+ *aDestroyedFramesFor = aChild;
+ }
+
+ if (aChild->IsHTMLElement(nsGkAtoms::body) ||
+ (!aContainer && aChild->IsElement())) {
+ // This might be the element we propagated viewport scrollbar
+ // styles from. Recompute those.
+ mPresShell->GetPresContext()->UpdateViewportScrollbarStylesOverride();
+ }
+
+ // XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and
+ // the :empty pseudo-class?
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentRemoved container=%p child=%p "
+ "old-next-sibling=%p\n",
+ static_cast<void*>(aContainer),
+ static_cast<void*>(aChild),
+ static_cast<void*>(aOldNextSibling));
+ if (gReallyNoisyContentUpdates) {
+ aContainer->List(stdout, 0);
+ }
+ }
+#endif
+
+ nsresult rv = NS_OK;
+ nsIFrame* childFrame = aChild->GetPrimaryFrame();
+ if (!childFrame || childFrame->GetContent() != aChild) {
+ // XXXbz the GetContent() != aChild check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ ClearUndisplayedContentIn(aChild, aContainer);
+ }
+ MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
+ "display:contents nodes shouldn't have a frame");
+ if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
+ nsIFrame* ancestorFrame = nullptr;
+ nsIContent* ancestor = aContainer;
+ for (; ancestor; ancestor = ancestor->GetParent()) {
+ ancestorFrame = ancestor->GetPrimaryFrame();
+ if (ancestorFrame) {
+ break;
+ }
+ }
+ if (ancestorFrame) {
+ nsIFrame* contentInsertion = ancestorFrame->GetContentInsertionFrame();
+ if (ancestorFrame->GetGenConPseudos() ||
+ (contentInsertion && contentInsertion->GetGenConPseudos())) {
+ *aDidReconstruct = true;
+ LAYOUT_PHASE_TEMP_EXIT();
+ // XXXmats Can we recreate frames only for the ::after/::before content?
+ // XXX Perhaps even only those that belong to the aChild sub-tree?
+ RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return NS_OK;
+ }
+ }
+
+ FlattenedChildIterator iter(aChild);
+ for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
+ if (c->GetPrimaryFrame() || GetDisplayContentsStyleFor(c)) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ rv = ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aFlags != REMOVE_DESTROY_FRAMES && *aDidReconstruct) {
+ return rv;
+ }
+ }
+ }
+ ClearDisplayContentsIn(aChild, aContainer);
+ }
+
+ nsPresContext* presContext = mPresShell->GetPresContext();
+#ifdef MOZ_XUL
+ if (NotifyListBoxBody(presContext, aContainer, aChild, aOldNextSibling,
+ childFrame, CONTENT_REMOVED)) {
+ if (aFlags == REMOVE_DESTROY_FRAMES) {
+ CaptureStateForFramesOf(aChild, mTempFrameTreeState);
+ }
+ return NS_OK;
+ }
+
+#endif // MOZ_XUL
+
+ // If we're removing the root, then make sure to remove things starting at
+ // the viewport's child instead of the primary frame (which might even be
+ // null if the root had an XBL binding or display:none, even though the
+ // frames above it got created). We do the adjustment after the childFrame
+ // check above, because we do want to clear any undisplayed content we might
+ // have for the root. Detecting removal of a root is a little exciting; in
+ // particular, having a null aContainer is necessary but NOT sufficient. Due
+ // to how we process reframes, the content node might not even be in our
+ // document by now. So explicitly check whether the viewport's first kid's
+ // content node is aChild.
+ bool isRoot = false;
+ if (!aContainer) {
+ nsIFrame* viewport = GetRootFrame();
+ if (viewport) {
+ nsIFrame* firstChild = viewport->PrincipalChildList().FirstChild();
+ if (firstChild && firstChild->GetContent() == aChild) {
+ isRoot = true;
+ childFrame = firstChild;
+ NS_ASSERTION(!childFrame->GetNextSibling(), "How did that happen?");
+ }
+ }
+ }
+
+ if (aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
+ !aContainer->IsInNativeAnonymousSubtree() &&
+ !aChild->IsInNativeAnonymousSubtree()) {
+ // Recreate frames if content is removed from a ShadowRoot
+ // because it may contain an insertion point which can change
+ // how the host is rendered.
+ //XXXsmaug This is super unefficient!
+ nsIContent* bindingParent = aContainer->GetBindingParent();
+ *aDidReconstruct = true;
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(bindingParent, false,
+ aFlags, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ if (aFlags == REMOVE_DESTROY_FRAMES) {
+ CaptureStateForFramesOf(aChild, mTempFrameTreeState);
+ }
+
+ if (childFrame) {
+ InvalidateCanvasIfNeeded(mPresShell, aChild);
+
+ // See whether we need to remove more than just childFrame
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsIContent* container;
+ if (MaybeRecreateContainerForFrameRemoval(childFrame, aFlags, &rv, &container)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ MOZ_ASSERT(container);
+ *aDidReconstruct = true;
+ if (aDestroyedFramesFor) {
+ *aDestroyedFramesFor = container;
+ }
+ return rv;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // Get the childFrame's parent frame
+ nsIFrame* parentFrame = childFrame->GetParent();
+ nsIAtom* parentType = parentFrame->GetType();
+
+ if (parentType == nsGkAtoms::frameSetFrame &&
+ IsSpecialFramesetChild(aChild)) {
+ // Just reframe the parent, since framesets are weird like that.
+ *aDidReconstruct = true;
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false,
+ aFlags, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // If we're a child of MathML, then we should reframe the MathML content.
+ // If we're non-MathML, then we would be wrapped in a block so we need to
+ // check our grandparent in that case.
+ nsIFrame* possibleMathMLAncestor = parentType == nsGkAtoms::blockFrame ?
+ parentFrame->GetParent() : parentFrame;
+ if (possibleMathMLAncestor->IsFrameOfType(nsIFrame::eMathML)) {
+ *aDidReconstruct = true;
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(possibleMathMLAncestor->GetContent(),
+ false, aFlags, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // Undo XUL wrapping if it's no longer needed.
+ // (If we're in the XUL block-wrapping situation, parentFrame is the
+ // wrapper frame.)
+ nsIFrame* grandparentFrame = parentFrame->GetParent();
+ if (grandparentFrame && grandparentFrame->IsXULBoxFrame() &&
+ (grandparentFrame->GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) &&
+ // check if this frame is the only one needing wrapping
+ aChild == AnyKidsNeedBlockParent(parentFrame->PrincipalChildList().FirstChild()) &&
+ !AnyKidsNeedBlockParent(childFrame->GetNextSibling())) {
+ *aDidReconstruct = true;
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(grandparentFrame->GetContent(), true,
+ aFlags, aDestroyedFramesFor);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->ContentRemoved(mPresShell, aChild);
+ }
+#endif
+
+ // Examine the containing-block for the removed content and see if
+ // :first-letter style applies.
+ nsIFrame* inflowChild = childFrame;
+ if (childFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ inflowChild = GetPlaceholderFrameFor(childFrame);
+ NS_ASSERTION(inflowChild, "No placeholder for out-of-flow?");
+ }
+ nsContainerFrame* containingBlock =
+ GetFloatContainingBlock(inflowChild->GetParent());
+ bool haveFLS = containingBlock && HasFirstLetterStyle(containingBlock);
+ if (haveFLS) {
+ // Trap out to special routine that handles adjusting a blocks
+ // frame tree when first-letter style is present.
+#ifdef NOISY_FIRST_LETTER
+ printf("ContentRemoved: containingBlock=");
+ nsFrame::ListTag(stdout, containingBlock);
+ printf(" parentFrame=");
+ nsFrame::ListTag(stdout, parentFrame);
+ printf(" childFrame=");
+ nsFrame::ListTag(stdout, childFrame);
+ printf("\n");
+#endif
+
+ // First update the containing blocks structure by removing the
+ // existing letter frames. This makes the subsequent logic
+ // simpler.
+ RemoveLetterFrames(mPresShell, containingBlock);
+
+ // Recover childFrame and parentFrame
+ childFrame = aChild->GetPrimaryFrame();
+ if (!childFrame || childFrame->GetContent() != aChild) {
+ // XXXbz the GetContent() != aChild check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ ClearUndisplayedContentIn(aChild, aContainer);
+ return NS_OK;
+ }
+ parentFrame = childFrame->GetParent();
+ parentType = parentFrame->GetType();
+
+#ifdef NOISY_FIRST_LETTER
+ printf(" ==> revised parentFrame=");
+ nsFrame::ListTag(stdout, parentFrame);
+ printf(" childFrame=");
+ nsFrame::ListTag(stdout, childFrame);
+ printf("\n");
+#endif
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentRemoved: childFrame=");
+ nsFrame::ListTag(stdout, childFrame);
+ putchar('\n');
+ parentFrame->List(stdout, 0);
+ }
+#endif
+
+
+ // Notify the parent frame that it should delete the frame
+ if (childFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ childFrame = GetPlaceholderFrameFor(childFrame);
+ NS_ASSERTION(childFrame, "Missing placeholder frame for out of flow.");
+ parentFrame = childFrame->GetParent();
+ }
+ RemoveFrame(nsLayoutUtils::GetChildListNameFor(childFrame), childFrame);
+
+ if (isRoot) {
+ mRootElementFrame = nullptr;
+ mRootElementStyleFrame = nullptr;
+ mDocElementContainingBlock = nullptr;
+ mPageSequenceFrame = nullptr;
+ mGfxScrollFrame = nullptr;
+ mHasRootAbsPosContainingBlock = false;
+ }
+
+ if (haveFLS && mRootElementFrame) {
+ RecoverLetterFrames(containingBlock);
+ }
+
+ // If we're just reconstructing frames for the element, then the
+ // following ContentInserted notification on the element will
+ // take care of fixing up any adjacent text nodes. We don't need
+ // to do this if the table parent type of our parent type is not
+ // eTypeBlock, though, because in that case the whitespace isn't
+ // being suppressed due to us anyway.
+ if (aContainer && !aChild->IsRootOfAnonymousSubtree() &&
+ aFlags == REMOVE_CONTENT &&
+ GetParentType(parentType) == eTypeBlock) {
+ // Adjacent whitespace-only text nodes might have been suppressed if
+ // this node does not have inline ends. Create frames for them now
+ // if necessary.
+ // Reframe any text node just before the node being removed, if there is
+ // one, and if it's not the last child or the first child. If a whitespace
+ // textframe was being suppressed and it's now the last child or first
+ // child then it can stay suppressed since the parent must be a block
+ // and hence it's adjacent to a block end.
+ // If aOldNextSibling is null, then the text node before the node being
+ // removed is the last node, and we don't need to worry about it.
+ if (aOldNextSibling) {
+ nsIContent* prevSibling = aOldNextSibling->GetPreviousSibling();
+ if (prevSibling && prevSibling->GetPreviousSibling()) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ ReframeTextIfNeeded(aContainer, prevSibling);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+ }
+ // Reframe any text node just after the node being removed, if there is
+ // one, and if it's not the last child or the first child.
+ if (aOldNextSibling && aOldNextSibling->GetNextSibling() &&
+ aOldNextSibling->GetPreviousSibling()) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ ReframeTextIfNeeded(aContainer, aOldNextSibling);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates && parentFrame) {
+ printf("nsCSSFrameConstructor::ContentRemoved: resulting frame model:\n");
+ parentFrame->List(stdout, 0);
+ }
+#endif
+ }
+
+ return rv;
+}
+
+/**
+ * This method invalidates the canvas when frames are removed or added for a
+ * node that might have its background propagated to the canvas, i.e., a
+ * document root node or an HTML BODY which is a child of the root node.
+ *
+ * @param aFrame a frame for a content node about to be removed or a frame that
+ * was just created for a content node that was inserted.
+ */
+static void
+InvalidateCanvasIfNeeded(nsIPresShell* presShell, nsIContent* node)
+{
+ NS_PRECONDITION(presShell->GetRootFrame(), "What happened here?");
+ NS_PRECONDITION(presShell->GetPresContext(), "Say what?");
+
+ // Note that both in ContentRemoved and ContentInserted the content node
+ // will still have the right parent pointer, so looking at that is ok.
+
+ nsIContent* parent = node->GetParent();
+ if (parent) {
+ // Has a parent; might not be what we want
+ nsIContent* grandParent = parent->GetParent();
+ if (grandParent) {
+ // Has a grandparent, so not what we want
+ return;
+ }
+
+ // Check whether it's an HTML body
+ if (!node->IsHTMLElement(nsGkAtoms::body)) {
+ return;
+ }
+ }
+
+ // At this point the node has no parent or it's an HTML <body> child of the
+ // root. We might not need to invalidate in this case (eg we might be in
+ // XHTML or something), but chances are we want to. Play it safe.
+ // Invalidate the viewport.
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ rootFrame->InvalidateFrameSubtree();
+}
+
+nsIFrame*
+nsCSSFrameConstructor::EnsureFrameForTextNode(nsGenericDOMDataNode* aContent)
+{
+ if (aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
+ !mAlwaysCreateFramesForIgnorableWhitespace) {
+ // Text frame may have been suppressed. Disable suppression and signal
+ // that a flush should be performed. We do this on a document-wide
+ // basis so that pages that repeatedly query metrics for
+ // collapsed-whitespace text nodes don't trigger pathological behavior.
+ mAlwaysCreateFramesForIgnorableWhitespace = true;
+ nsAutoScriptBlocker blocker;
+ BeginUpdate();
+ ReconstructDocElementHierarchy();
+ EndUpdate();
+ }
+ return aContent->GetPrimaryFrame();
+}
+
+nsresult
+nsCSSFrameConstructor::CharacterDataChanged(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+ nsresult rv = NS_OK;
+
+ if ((aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
+ !aContent->TextIsOnlyWhitespace()) ||
+ (aContent->HasFlag(NS_REFRAME_IF_WHITESPACE) &&
+ aContent->TextIsOnlyWhitespace())) {
+#ifdef DEBUG
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ NS_ASSERTION(!frame || !frame->IsGeneratedContentFrame(),
+ "Bit should never be set on generated content");
+#endif
+ LAYOUT_PHASE_TEMP_EXIT();
+ nsresult rv = RecreateFramesForContent(aContent, false,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return rv;
+ }
+
+ // Find the child frame
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+
+ // Notify the first frame that maps the content. It will generate a reflow
+ // command
+
+ // It's possible the frame whose content changed isn't inserted into the
+ // frame hierarchy yet, or that there is no frame that maps the content
+ if (nullptr != frame) {
+#if 0
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("nsCSSFrameConstructor::CharacterDataChanged: content=%p[%s] subcontent=%p frame=%p",
+ aContent, ContentTag(aContent, 0),
+ aSubContent, frame));
+#endif
+
+ // Special check for text content that is a child of a letter frame. If
+ // this happens, we should remove the letter frame, do whatever we're
+ // planning to do with this notification, then put the letter frame back.
+ // Note that this is basically what RecreateFramesForContent ends up doing;
+ // the reason we dont' want to call that here is that our text content
+ // could be native anonymous, in which case RecreateFramesForContent would
+ // completely barf on it. And recreating the non-anonymous ancestor would
+ // just lead us to come back into this notification (e.g. if quotes or
+ // counters are involved), leading to a loop.
+ nsContainerFrame* block = GetFloatContainingBlock(frame);
+ bool haveFirstLetterStyle = false;
+ if (block) {
+ // See if the block has first-letter style applied to it.
+ haveFirstLetterStyle = HasFirstLetterStyle(block);
+ if (haveFirstLetterStyle) {
+ RemoveLetterFrames(mPresShell, block);
+ // Reget |frame|, since we might have killed it.
+ // Do we really need to call CharacterDataChanged in this case, though?
+ frame = aContent->GetPrimaryFrame();
+ NS_ASSERTION(frame, "Should have frame here!");
+ }
+ }
+
+ frame->CharacterDataChanged(aInfo);
+
+ if (haveFirstLetterStyle) {
+ RecoverLetterFrames(block);
+ }
+ }
+
+ return rv;
+}
+
+void
+nsCSSFrameConstructor::BeginUpdate() {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+
+ nsRootPresContext* rootPresContext =
+ mPresShell->GetPresContext()->GetRootPresContext();
+ if (rootPresContext) {
+ rootPresContext->IncrementDOMGeneration();
+ }
+
+ ++sGlobalGenerationNumber;
+#ifdef DEBUG
+ ++mUpdateCount;
+#endif
+}
+
+void
+nsCSSFrameConstructor::EndUpdate()
+{
+#ifdef DEBUG
+ NS_ASSERTION(mUpdateCount, "Negative mUpdateCount!");
+ --mUpdateCount;
+#endif
+}
+
+void
+nsCSSFrameConstructor::RecalcQuotesAndCounters()
+{
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mQuotesDirty) {
+ mQuotesDirty = false;
+ mQuoteList.RecalcAll();
+ }
+
+ if (mCountersDirty) {
+ mCountersDirty = false;
+ mCounterManager.RecalcAll();
+ }
+
+ NS_ASSERTION(!mQuotesDirty, "Quotes updates will be lost");
+ NS_ASSERTION(!mCountersDirty, "Counter updates will be lost");
+}
+
+void
+nsCSSFrameConstructor::NotifyCounterStylesAreDirty()
+{
+ NS_PRECONDITION(mUpdateCount != 0, "Should be in an update");
+ mCounterManager.SetAllCounterStylesDirty();
+ CountersDirty();
+}
+
+void
+nsCSSFrameConstructor::WillDestroyFrameTree()
+{
+#if defined(DEBUG_dbaron_off)
+ mCounterManager.Dump();
+#endif
+
+ mIsDestroyingFrameTree = true;
+
+ // Prevent frame tree destruction from being O(N^2)
+ mQuoteList.Clear();
+ mCounterManager.Clear();
+
+ // Remove our presshell as a style flush observer. But leave
+ // RestyleManager::mObservingRefreshDriver true so we don't readd to
+ // it even if someone tries to post restyle events on us from this
+ // point on for some reason.
+ mPresShell->GetPresContext()->RefreshDriver()->
+ RemoveStyleFlushObserver(mPresShell);
+
+ nsFrameManager::Destroy();
+}
+
+//STATIC
+
+// XXXbz I'd really like this method to go away. Once we have inline-block and
+// I can just use that for sized broken images, that can happen, maybe.
+void nsCSSFrameConstructor::GetAlternateTextFor(nsIContent* aContent,
+ nsIAtom* aTag, // content object's tag
+ nsXPIDLString& aAltText)
+{
+ // The "alt" attribute specifies alternate text that is rendered
+ // when the image can not be displayed
+
+ // If there's no "alt" attribute, and aContent is an input
+ // element, then use the value of the "value" attribute
+ if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aAltText) &&
+ nsGkAtoms::input == aTag) {
+ // If there's no "value" attribute either, then use the localized string
+ // for "Submit" as the alternate text.
+ if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aAltText)) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "Submit", aAltText);
+ }
+ }
+}
+
+nsIFrame*
+nsCSSFrameConstructor::CreateContinuingOuterTableFrame(nsIPresShell* aPresShell,
+ nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ nsTableWrapperFrame* newFrame = NS_NewTableWrapperFrame(aPresShell, aStyleContext);
+
+ newFrame->Init(aContent, aParentFrame, aFrame);
+
+ // Create a continuing inner table frame, and if there's a caption then
+ // replicate the caption
+ nsFrameItems newChildFrames;
+
+ nsIFrame* childFrame = aFrame->PrincipalChildList().FirstChild();
+ if (childFrame) {
+ nsIFrame* continuingTableFrame =
+ CreateContinuingFrame(aPresContext, childFrame, newFrame);
+ newChildFrames.AddChild(continuingTableFrame);
+
+ NS_ASSERTION(!childFrame->GetNextSibling(),"there can be only one inner table frame");
+ }
+
+ // Set the table wrapper's initial child list
+ newFrame->SetInitialChildList(kPrincipalList, newChildFrames);
+
+ return newFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::CreateContinuingTableFrame(nsIPresShell* aPresShell,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ nsTableFrame* newFrame = NS_NewTableFrame(aPresShell, aStyleContext);
+
+ newFrame->Init(aContent, aParentFrame, aFrame);
+
+ // Replicate any header/footer frames
+ nsFrameItems childFrames;
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ // See if it's a header/footer, possibly wrapped in a scroll frame.
+ nsTableRowGroupFrame* rowGroupFrame =
+ static_cast<nsTableRowGroupFrame*>(childFrame);
+ // If the row group was continued, then don't replicate it.
+ nsIFrame* rgNextInFlow = rowGroupFrame->GetNextInFlow();
+ if (rgNextInFlow) {
+ rowGroupFrame->SetRepeatable(false);
+ }
+ else if (rowGroupFrame->IsRepeatable()) {
+ // Replicate the header/footer frame.
+ nsTableRowGroupFrame* headerFooterFrame;
+ nsFrameItems childItems;
+ nsFrameConstructorState state(mPresShell,
+ GetAbsoluteContainingBlock(newFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(newFrame, ABS_POS),
+ nullptr);
+ state.mCreatingExtraFrames = true;
+
+ nsStyleContext* const headerFooterStyleContext = rowGroupFrame->StyleContext();
+ headerFooterFrame = static_cast<nsTableRowGroupFrame*>
+ (NS_NewTableRowGroupFrame(aPresShell, headerFooterStyleContext));
+
+ nsIContent* headerFooter = rowGroupFrame->GetContent();
+ headerFooterFrame->Init(headerFooter, newFrame, nullptr);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlockIfNeeded(state,
+ headerFooterStyleContext->StyleDisplay(),
+ absoluteSaveState,
+ headerFooterFrame);
+
+ ProcessChildren(state, headerFooter, rowGroupFrame->StyleContext(),
+ headerFooterFrame, true, childItems, false,
+ nullptr);
+ NS_ASSERTION(state.mFloatedItems.IsEmpty(), "unexpected floated element");
+ headerFooterFrame->SetInitialChildList(kPrincipalList, childItems);
+ headerFooterFrame->SetRepeatable(true);
+
+ // Table specific initialization
+ headerFooterFrame->InitRepeatedFrame(rowGroupFrame);
+
+ // XXX Deal with absolute and fixed frames...
+ childFrames.AddChild(headerFooterFrame);
+ }
+ }
+
+ // Set the table frame's initial child list
+ newFrame->SetInitialChildList(kPrincipalList, childFrames);
+
+ return newFrame;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::CreateContinuingFrame(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ bool aIsFluid)
+{
+ nsIPresShell* shell = aPresContext->PresShell();
+ nsStyleContext* styleContext = aFrame->StyleContext();
+ nsIFrame* newFrame = nullptr;
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+ nsIFrame* nextInFlow = aFrame->GetNextInFlow();
+
+ // Use the frame type to determine what type of frame to create
+ nsIAtom* frameType = aFrame->GetType();
+ nsIContent* content = aFrame->GetContent();
+
+ NS_ASSERTION(aFrame->GetSplittableType() != NS_FRAME_NOT_SPLITTABLE,
+ "why CreateContinuingFrame for a non-splittable frame?");
+
+ if (nsGkAtoms::textFrame == frameType) {
+ newFrame = NS_NewContinuingTextFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::inlineFrame == frameType) {
+ newFrame = NS_NewInlineFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::blockFrame == frameType) {
+ MOZ_ASSERT(!aFrame->IsTableCaption(),
+ "no support for fragmenting table captions yet");
+ newFrame = NS_NewBlockFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+#ifdef MOZ_XUL
+ } else if (nsGkAtoms::XULLabelFrame == frameType) {
+ newFrame = NS_NewXULLabelFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+#endif
+ } else if (nsGkAtoms::columnSetFrame == frameType) {
+ MOZ_ASSERT(!aFrame->IsTableCaption(),
+ "no support for fragmenting table captions yet");
+ newFrame = NS_NewColumnSetFrame(shell, styleContext, nsFrameState(0));
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::pageFrame == frameType) {
+ nsContainerFrame* canvasFrame;
+ newFrame = ConstructPageFrame(shell, aParentFrame, aFrame, canvasFrame);
+ } else if (nsGkAtoms::tableWrapperFrame == frameType) {
+ newFrame =
+ CreateContinuingOuterTableFrame(shell, aPresContext, aFrame, aParentFrame,
+ content, styleContext);
+
+ } else if (nsGkAtoms::tableFrame == frameType) {
+ newFrame =
+ CreateContinuingTableFrame(shell, aFrame, aParentFrame,
+ content, styleContext);
+
+ } else if (nsGkAtoms::tableRowGroupFrame == frameType) {
+ newFrame = NS_NewTableRowGroupFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ if (newFrame->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) {
+ nsTableFrame::RegisterPositionedTablePart(newFrame);
+ }
+ } else if (nsGkAtoms::tableRowFrame == frameType) {
+ nsTableRowFrame* rowFrame = NS_NewTableRowFrame(shell, styleContext);
+
+ rowFrame->Init(content, aParentFrame, aFrame);
+ if (rowFrame->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) {
+ nsTableFrame::RegisterPositionedTablePart(rowFrame);
+ }
+
+ // Create a continuing frame for each table cell frame
+ nsFrameItems newChildList;
+ nsIFrame* cellFrame = aFrame->PrincipalChildList().FirstChild();
+ while (cellFrame) {
+ // See if it's a table cell frame
+ if (IS_TABLE_CELL(cellFrame->GetType())) {
+ nsIFrame* continuingCellFrame =
+ CreateContinuingFrame(aPresContext, cellFrame, rowFrame);
+ newChildList.AddChild(continuingCellFrame);
+ }
+ cellFrame = cellFrame->GetNextSibling();
+ }
+
+ rowFrame->SetInitialChildList(kPrincipalList, newChildList);
+ newFrame = rowFrame;
+
+ } else if (IS_TABLE_CELL(frameType)) {
+ // Warning: If you change this and add a wrapper frame around table cell
+ // frames, make sure Bug 368554 doesn't regress!
+ // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
+ nsTableFrame* tableFrame =
+ static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
+ nsTableCellFrame* cellFrame =
+ NS_NewTableCellFrame(shell, styleContext, tableFrame);
+
+ cellFrame->Init(content, aParentFrame, aFrame);
+ if (cellFrame->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) {
+ nsTableFrame::RegisterPositionedTablePart(cellFrame);
+ }
+
+ // Create a continuing area frame
+ nsIFrame* blockFrame = aFrame->PrincipalChildList().FirstChild();
+ nsIFrame* continuingBlockFrame =
+ CreateContinuingFrame(aPresContext, blockFrame,
+ static_cast<nsContainerFrame*>(cellFrame));
+
+ SetInitialSingleChild(cellFrame, continuingBlockFrame);
+ newFrame = cellFrame;
+ } else if (nsGkAtoms::lineFrame == frameType) {
+ newFrame = NS_NewFirstLineFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::letterFrame == frameType) {
+ newFrame = NS_NewFirstLetterFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::imageFrame == frameType) {
+ newFrame = NS_NewImageFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::imageControlFrame == frameType) {
+ newFrame = NS_NewImageControlFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::placeholderFrame == frameType) {
+ // create a continuing out of flow frame
+ nsIFrame* oofFrame = nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ nsIFrame* oofContFrame =
+ CreateContinuingFrame(aPresContext, oofFrame, aParentFrame);
+ newFrame =
+ CreatePlaceholderFrameFor(shell, content, oofContFrame,
+ styleContext->GetParent(),
+ aParentFrame, aFrame,
+ aFrame->GetStateBits() & PLACEHOLDER_TYPE_MASK);
+ } else if (nsGkAtoms::fieldSetFrame == frameType) {
+ nsContainerFrame* fieldset = NS_NewFieldSetFrame(shell, styleContext);
+
+ fieldset->Init(content, aParentFrame, aFrame);
+
+ // Create a continuing area frame
+ // XXXbz we really shouldn't have to do this by hand!
+ nsContainerFrame* blockFrame = GetFieldSetBlockFrame(aFrame);
+ if (blockFrame) {
+ nsIFrame* continuingBlockFrame =
+ CreateContinuingFrame(aPresContext, blockFrame, fieldset);
+ // Set the fieldset's initial child list
+ SetInitialSingleChild(fieldset, continuingBlockFrame);
+ } else {
+ MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER,
+ "FieldSet block may only be null for overflow containers");
+ }
+ newFrame = fieldset;
+ } else if (nsGkAtoms::legendFrame == frameType) {
+ newFrame = NS_NewLegendFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::flexContainerFrame == frameType) {
+ newFrame = NS_NewFlexContainerFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::gridContainerFrame == frameType) {
+ newFrame = NS_NewGridContainerFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::rubyFrame == frameType) {
+ newFrame = NS_NewRubyFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::rubyBaseContainerFrame == frameType) {
+ newFrame = NS_NewRubyBaseContainerFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::rubyTextContainerFrame == frameType) {
+ newFrame = NS_NewRubyTextContainerFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (nsGkAtoms::detailsFrame == frameType) {
+ newFrame = NS_NewDetailsFrame(shell, styleContext);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else {
+ NS_RUNTIMEABORT("unexpected frame type");
+ }
+
+ // Init() set newFrame to be a fluid continuation of aFrame.
+ // If we want a non-fluid continuation, we need to call SetPrevContinuation()
+ // to reset NS_FRAME_IS_FLUID_CONTINUATION.
+ if (!aIsFluid) {
+ newFrame->SetPrevContinuation(aFrame);
+ }
+
+ // A continuation of generated content is also generated content
+ if (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT) {
+ newFrame->AddStateBits(NS_FRAME_GENERATED_CONTENT);
+ }
+
+ // A continuation of nsIAnonymousContentCreator content is also
+ // nsIAnonymousContentCreator created content
+ if (aFrame->GetStateBits() & NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT) {
+ newFrame->AddStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT);
+ }
+
+ // A continuation of an out-of-flow is also an out-of-flow
+ if (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ newFrame->AddStateBits(NS_FRAME_OUT_OF_FLOW);
+ }
+
+ if (nextInFlow) {
+ nextInFlow->SetPrevInFlow(newFrame);
+ newFrame->SetNextInFlow(nextInFlow);
+ } else if (nextContinuation) {
+ nextContinuation->SetPrevContinuation(newFrame);
+ newFrame->SetNextContinuation(nextContinuation);
+ }
+
+ NS_POSTCONDITION(!newFrame->GetNextSibling(), "unexpected sibling");
+ return newFrame;
+}
+
+nsresult
+nsCSSFrameConstructor::ReplicateFixedFrames(nsPageContentFrame* aParentFrame)
+{
+ // Now deal with fixed-pos things.... They should appear on all pages,
+ // so we want to move over the placeholders when processing the child
+ // of the pageContentFrame.
+
+ nsIFrame* prevPageContentFrame = aParentFrame->GetPrevInFlow();
+ if (!prevPageContentFrame) {
+ return NS_OK;
+ }
+ nsContainerFrame* canvasFrame =
+ do_QueryFrame(aParentFrame->PrincipalChildList().FirstChild());
+ nsIFrame* prevCanvasFrame = prevPageContentFrame->PrincipalChildList().FirstChild();
+ if (!canvasFrame || !prevCanvasFrame) {
+ // document's root element frame missing
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsFrameItems fixedPlaceholders;
+ nsIFrame* firstFixed = prevPageContentFrame->GetChildList(nsIFrame::kFixedList).FirstChild();
+ if (!firstFixed) {
+ return NS_OK;
+ }
+
+ // Don't allow abs-pos descendants of the fixed content to escape the content.
+ // This should not normally be possible (because fixed-pos elements should
+ // be absolute containers) but fixed-pos tables currently aren't abs-pos
+ // containers.
+ nsFrameConstructorState state(mPresShell, aParentFrame,
+ nullptr,
+ mRootElementFrame);
+ state.mCreatingExtraFrames = true;
+
+ // We can't use an ancestor filter here, because we're not going to
+ // be usefully recurring down the tree. This means that other
+ // places in frame construction can't assume a filter is
+ // initialized!
+
+ // Iterate across fixed frames and replicate each whose placeholder is a
+ // descendant of aFrame. (We don't want to explicitly copy placeholders that
+ // are within fixed frames, because that would cause duplicates on the new
+ // page - bug 389619)
+ for (nsIFrame* fixed = firstFixed; fixed; fixed = fixed->GetNextSibling()) {
+ nsIFrame* prevPlaceholder = GetPlaceholderFrameFor(fixed);
+ if (prevPlaceholder &&
+ nsLayoutUtils::IsProperAncestorFrame(prevCanvasFrame, prevPlaceholder)) {
+ // We want to use the same style as the primary style frame for
+ // our content
+ nsIContent* content = fixed->GetContent();
+ nsStyleContext* styleContext =
+ nsLayoutUtils::GetStyleFrame(content)->StyleContext();
+ FrameConstructionItemList items;
+ AddFrameConstructionItemsInternal(state, content, canvasFrame,
+ content->NodeInfo()->NameAtom(),
+ content->GetNameSpaceID(),
+ true,
+ styleContext,
+ ITEM_ALLOW_XBL_BASE |
+ ITEM_ALLOW_PAGE_BREAK,
+ nullptr, items);
+ ConstructFramesFromItemList(state, items, canvasFrame, fixedPlaceholders);
+ }
+ }
+
+ // Add the placeholders to our primary child list.
+ // XXXbz this is a little screwed up, since the fixed frames will have
+ // broken auto-positioning. Oh, well.
+ NS_ASSERTION(!canvasFrame->PrincipalChildList().FirstChild(),
+ "leaking frames; doc root continuation must be empty");
+ canvasFrame->SetInitialChildList(kPrincipalList, fixedPlaceholders);
+ return NS_OK;
+}
+
+nsCSSFrameConstructor::InsertionPoint
+nsCSSFrameConstructor::GetInsertionPoint(nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ nsBindingManager* bindingManager = mDocument->BindingManager();
+
+ nsIContent* insertionElement;
+ if (aChild) {
+ // We've got an explicit insertion child. Check to see if it's
+ // anonymous.
+ if (aChild->GetBindingParent() == aContainer) {
+ // This child content is anonymous. Don't use the insertion
+ // point, since that's only for the explicit kids.
+ return InsertionPoint(GetContentInsertionFrameFor(aContainer), aContainer);
+ }
+
+ if (nsContentUtils::HasDistributedChildren(aContainer)) {
+ // The container distributes nodes, use the frame of the flattened tree parent.
+ // It may be the case that the node is distributed but not matched to any
+ // insertion points, so there is no flattened parent.
+ nsIContent* flattenedParent = aChild->GetFlattenedTreeParent();
+ if (flattenedParent) {
+ return InsertionPoint(GetContentInsertionFrameFor(flattenedParent),
+ flattenedParent);
+ }
+ return InsertionPoint();
+ }
+
+ insertionElement = bindingManager->FindNestedInsertionPoint(aContainer, aChild);
+ } else {
+ if (nsContentUtils::HasDistributedChildren(aContainer)) {
+ // The container distributes nodes to shadow DOM insertion points.
+ // Return with aMultiple set to true to induce callers to insert children
+ // individually into the node's flattened tree parent.
+ return InsertionPoint(nullptr, nullptr, true);
+ }
+
+ bool multiple;
+ insertionElement = bindingManager->FindNestedSingleInsertionPoint(aContainer, &multiple);
+ if (multiple) {
+ return InsertionPoint(nullptr, nullptr, true);
+ }
+ }
+
+ if (!insertionElement) {
+ insertionElement = aContainer;
+ }
+ InsertionPoint insertion(GetContentInsertionFrameFor(insertionElement),
+ insertionElement);
+
+ // Fieldset frames have multiple normal flow child frame lists so handle it
+ // the same as if it had multiple content insertion points.
+ if (insertion.mParentFrame &&
+ insertion.mParentFrame->GetType() == nsGkAtoms::fieldSetFrame) {
+ insertion.mMultiple = true;
+ }
+
+ // A details frame moves the first summary frame to be its first child, so we
+ // treat it as if it has multiple content insertion points.
+ if (insertion.mParentFrame &&
+ insertion.mParentFrame->GetType() == nsGkAtoms::detailsFrame) {
+ insertion.mMultiple = true;
+ }
+
+ return insertion;
+}
+
+// Capture state for the frame tree rooted at the frame associated with the
+// content object, aContent
+void
+nsCSSFrameConstructor::CaptureStateForFramesOf(nsIContent* aContent,
+ nsILayoutHistoryState* aHistoryState)
+{
+ if (!aHistoryState) {
+ return;
+ }
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame == mRootElementFrame) {
+ frame = mRootElementFrame ?
+ GetAbsoluteContainingBlock(mRootElementFrame, FIXED_POS) :
+ GetRootFrame();
+ }
+ for ( ; frame;
+ frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
+ CaptureFrameState(frame, aHistoryState);
+ }
+}
+
+static bool
+DefinitelyEqualURIsAndPrincipal(mozilla::css::URLValue* aURI1,
+ mozilla::css::URLValue* aURI2)
+{
+ return aURI1 == aURI2 ||
+ (aURI1 && aURI2 && aURI1->DefinitelyEqualURIsAndPrincipal(*aURI2));
+}
+
+nsStyleContext*
+nsCSSFrameConstructor::MaybeRecreateFramesForElement(Element* aElement)
+{
+ RefPtr<nsStyleContext> oldContext = GetUndisplayedContent(aElement);
+ StyleDisplay oldDisplay = StyleDisplay::None;
+ if (!oldContext) {
+ oldContext = GetDisplayContentsStyleFor(aElement);
+ if (!oldContext) {
+ return nullptr;
+ }
+ oldDisplay = StyleDisplay::Contents;
+ }
+
+ // The parent has a frame, so try resolving a new context.
+ RefPtr<nsStyleContext> newContext = mPresShell->StyleSet()->
+ ResolveStyleFor(aElement, oldContext->GetParent());
+
+ if (oldDisplay == StyleDisplay::None) {
+ ChangeUndisplayedContent(aElement, newContext);
+ } else {
+ ChangeDisplayContents(aElement, newContext);
+ }
+
+ const nsStyleDisplay* disp = newContext->StyleDisplay();
+ if (oldDisplay == disp->mDisplay) {
+ // We can skip trying to recreate frames here, but only if our style
+ // context does not have a binding URI that differs from our old one.
+ // Otherwise, we should try to recreate, because we may want to apply the
+ // new binding
+ if (!disp->mBinding) {
+ return newContext;
+ }
+ const nsStyleDisplay* oldDisp = oldContext->PeekStyleDisplay();
+ if (oldDisp &&
+ DefinitelyEqualURIsAndPrincipal(disp->mBinding, oldDisp->mBinding)) {
+ return newContext;
+ }
+ }
+
+ RecreateFramesForContent(aElement, false, REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return nullptr;
+}
+
+static bool
+IsWhitespaceFrame(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame, "invalid argument");
+ return aFrame->GetType() == nsGkAtoms::textFrame &&
+ aFrame->GetContent()->TextIsOnlyWhitespace();
+}
+
+static nsIFrame*
+FindFirstNonWhitespaceChild(nsIFrame* aParentFrame)
+{
+ nsIFrame* f = aParentFrame->PrincipalChildList().FirstChild();
+ while (f && IsWhitespaceFrame(f)) {
+ f = f->GetNextSibling();
+ }
+ return f;
+}
+
+static nsIFrame*
+FindNextNonWhitespaceSibling(nsIFrame* aFrame)
+{
+ nsIFrame* f = aFrame;
+ do {
+ f = f->GetNextSibling();
+ } while (f && IsWhitespaceFrame(f));
+ return f;
+}
+
+static nsIFrame*
+FindPreviousNonWhitespaceSibling(nsIFrame* aFrame)
+{
+ nsIFrame* f = aFrame;
+ do {
+ f = f->GetPrevSibling();
+ } while (f && IsWhitespaceFrame(f));
+ return f;
+}
+
+bool
+nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame,
+ RemoveFlags aFlags,
+ nsresult* aResult,
+ nsIContent** aDestroyedFramesFor)
+{
+ NS_PRECONDITION(aFrame, "Must have a frame");
+ NS_PRECONDITION(aFrame->GetParent(), "Frame shouldn't be root");
+ NS_PRECONDITION(aResult, "Null out param?");
+ NS_PRECONDITION(aFrame == aFrame->FirstContinuation(),
+ "aFrame not the result of GetPrimaryFrame()?");
+
+ *aDestroyedFramesFor = nullptr;
+
+ if (IsFramePartOfIBSplit(aFrame)) {
+ // The removal functions can't handle removal of an {ib} split directly; we
+ // need to rebuild the containing block.
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
+ "frame=");
+ nsFrame::ListTag(stdout, aFrame);
+ printf(" is ib-split\n");
+ }
+#endif
+
+ *aResult = ReframeContainingBlock(aFrame, aFlags, aDestroyedFramesFor);
+ return true;
+ }
+
+ nsContainerFrame* insertionFrame = aFrame->GetContentInsertionFrame();
+ if (insertionFrame && insertionFrame->GetType() == nsGkAtoms::legendFrame &&
+ aFrame->GetParent()->GetType() == nsGkAtoms::fieldSetFrame) {
+ // When we remove the legend for a fieldset, we should reframe
+ // the fieldset to ensure another legend is used, if there is one
+ *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(), false,
+ aFlags, aDestroyedFramesFor);
+ return true;
+ }
+
+ if (insertionFrame &&
+ aFrame->GetParent()->GetType() == nsGkAtoms::detailsFrame) {
+ HTMLSummaryElement* summary =
+ HTMLSummaryElement::FromContent(insertionFrame->GetContent());
+
+ if (summary && summary->IsMainSummary()) {
+ // When removing a summary, we should reframe the parent details frame to
+ // ensure that another summary is used or the default summary is
+ // generated.
+ *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(),
+ false, REMOVE_FOR_RECONSTRUCTION,
+ aDestroyedFramesFor);
+ return true;
+ }
+ }
+
+ // Now check for possibly needing to reconstruct due to a pseudo parent
+ nsIFrame* inFlowFrame =
+ (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) ?
+ GetPlaceholderFrameFor(aFrame) : aFrame;
+ MOZ_ASSERT(inFlowFrame, "How did that happen?");
+ MOZ_ASSERT(inFlowFrame == inFlowFrame->FirstContinuation(),
+ "placeholder for primary frame has previous continuations?");
+ nsIFrame* parent = inFlowFrame->GetParent();
+ // For the case of ruby pseudo parent, effectively, only pseudo rb/rt frame
+ // need to be checked here, since all other types of parent will be catched
+ // by "Check ruby containers" section below.
+ if (IsTableOrRubyPseudo(parent)) {
+ if (FindFirstNonWhitespaceChild(parent) == inFlowFrame ||
+ !FindNextNonWhitespaceSibling(inFlowFrame->LastContinuation()) ||
+ // If it is a whitespace, and is the only child of the parent, the
+ // pseudo parent was created for the space, and should now be removed.
+ (IsWhitespaceFrame(aFrame) &&
+ parent->PrincipalChildList().OnlyChild()) ||
+ // If we're a table-column-group, then the OnlyChild check above is
+ // not going to catch cases when we're the first child.
+ (inFlowFrame->GetType() == nsGkAtoms::tableColGroupFrame &&
+ parent->GetChildList(nsIFrame::kColGroupList).FirstChild() == inFlowFrame) ||
+ // Similar if we're a table-caption.
+ (inFlowFrame->IsTableCaption() &&
+ parent->GetChildList(nsIFrame::kCaptionList).FirstChild() == inFlowFrame)) {
+ // We're the first or last frame in the pseudo. Need to reframe.
+ // Good enough to recreate frames for |parent|'s content
+ *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
+ aDestroyedFramesFor);
+ return true;
+ }
+ }
+
+ // Might need to reconstruct things if this frame's nextSibling is a table
+ // or ruby pseudo, since removal of this frame might mean that this pseudo
+ // needs to get merged with the frame's prevSibling if that's also a table
+ // or ruby pseudo.
+ nsIFrame* nextSibling =
+ FindNextNonWhitespaceSibling(inFlowFrame->LastContinuation());
+ NS_ASSERTION(!IsTableOrRubyPseudo(inFlowFrame), "Shouldn't happen here");
+ // Effectively, for the ruby pseudo sibling case, only pseudo <ruby> frame
+ // need to be checked here, since all other types of such frames will have
+ // a ruby container parent, and be catched by "Check ruby containers" below.
+ if (nextSibling && IsTableOrRubyPseudo(nextSibling)) {
+ nsIFrame* prevSibling = FindPreviousNonWhitespaceSibling(inFlowFrame);
+ if (prevSibling && IsTableOrRubyPseudo(prevSibling)) {
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
+ "frame=");
+ nsFrame::ListTag(stdout, aFrame);
+ printf(" has a table pseudo next sibling of different type and a "
+ "table pseudo prevsibling\n");
+ }
+#endif
+ // Good enough to recreate frames for aFrame's parent's content; even if
+ // aFrame's parent is a pseudo, that'll be the right content node.
+ *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
+ aDestroyedFramesFor);
+ return true;
+ }
+ }
+
+ // Check ruby containers
+ nsIAtom* parentType = parent->GetType();
+ if (parentType == nsGkAtoms::rubyFrame ||
+ RubyUtils::IsRubyContainerBox(parentType)) {
+ // In ruby containers, pseudo frames may be created from
+ // whitespaces or even nothing. There are two cases we actually
+ // need to handle here, but hard to check exactly:
+ // 1. Status of spaces beside the frame may vary, and related
+ // frames may be constructed or destroyed accordingly.
+ // 2. The type of the first child of a ruby frame determines
+ // whether a pseudo ruby base container should exist.
+ *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
+ aDestroyedFramesFor);
+ return true;
+ }
+
+ // Might need to reconstruct things if the removed frame's nextSibling is an
+ // anonymous flex item. The removed frame might've been what divided two
+ // runs of inline content into two anonymous flex items, which would now
+ // need to be merged.
+ // NOTE: It's fine that we've advanced nextSibling past whitespace (up above);
+ // we're only interested in anonymous flex items here, and those can never
+ // be adjacent to whitespace, since they absorb contiguous runs of inline
+ // non-replaced content (including whitespace).
+ if (nextSibling && IsAnonymousFlexOrGridItem(nextSibling)) {
+ AssertAnonymousFlexOrGridItemParent(nextSibling, parent);
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
+ "frame=");
+ nsFrame::ListTag(stdout, aFrame);
+ printf(" has an anonymous flex item as its next sibling\n");
+ }
+#endif // DEBUG
+ // Recreate frames for the flex container (the removed frame's parent)
+ *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
+ aDestroyedFramesFor);
+ return true;
+ }
+
+ // Might need to reconstruct things if the removed frame's nextSibling is
+ // null and its parent is an anonymous flex item. (This might be the last
+ // remaining child of that anonymous flex item, which can then go away.)
+ if (!nextSibling && IsAnonymousFlexOrGridItem(parent)) {
+ AssertAnonymousFlexOrGridItemParent(parent, parent->GetParent());
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
+ "frame=");
+ nsFrame::ListTag(stdout, aFrame);
+ printf(" has an anonymous flex item as its parent\n");
+ }
+#endif // DEBUG
+ // Recreate frames for the flex container (the removed frame's grandparent)
+ *aResult = RecreateFramesForContent(parent->GetParent()->GetContent(), true,
+ aFlags, aDestroyedFramesFor);
+ return true;
+ }
+
+#ifdef MOZ_XUL
+ if (aFrame->GetType() == nsGkAtoms::popupSetFrame) {
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresShell);
+ if (rootBox && rootBox->GetPopupSetFrame() == aFrame) {
+ *aResult = ReconstructDocElementHierarchy();
+ return true;
+ }
+ }
+#endif
+
+ // Reconstruct if inflowFrame is parent's only child, and parent is, or has,
+ // a non-fluid continuation, i.e. it was split by bidi resolution
+ if (!inFlowFrame->GetPrevSibling() &&
+ !inFlowFrame->GetNextSibling() &&
+ ((parent->GetPrevContinuation() && !parent->GetPrevInFlow()) ||
+ (parent->GetNextContinuation() && !parent->GetNextInFlow()))) {
+ *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
+ aDestroyedFramesFor);
+ return true;
+ }
+
+ // We might still need to reconstruct things if the parent of inFlowFrame is
+ // ib-split, since in that case the removal of aFrame might affect the
+ // splitting of its parent.
+ if (!IsFramePartOfIBSplit(parent)) {
+ return false;
+ }
+
+ // If inFlowFrame is not the only in-flow child of |parent|, then removing
+ // it will change nothing about the {ib} split.
+ if (inFlowFrame != parent->PrincipalChildList().FirstChild() ||
+ inFlowFrame->LastContinuation()->GetNextSibling()) {
+ return false;
+ }
+
+ // If the parent is the first or last part of the {ib} split, then
+ // removing one of its kids will have no effect on the splitting.
+ // Get the first continuation up front so we don't have to do it twice.
+ nsIFrame* parentFirstContinuation = parent->FirstContinuation();
+ if (!GetIBSplitSibling(parentFirstContinuation) ||
+ !GetIBSplitPrevSibling(parentFirstContinuation)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
+ "frame=");
+ nsFrame::ListTag(stdout, parent);
+ printf(" is ib-split\n");
+ }
+#endif
+
+ *aResult = ReframeContainingBlock(parent, aFlags, aDestroyedFramesFor);
+ return true;
+}
+
+nsresult
+nsCSSFrameConstructor::RecreateFramesForContent(nsIContent* aContent,
+ bool aAsyncInsert,
+ RemoveFlags aFlags,
+ nsIContent** aDestroyedFramesFor)
+{
+ NS_PRECONDITION(!aAsyncInsert || aContent->IsElement(),
+ "Can only insert elements async");
+ // If there is no document, we don't want to recreate frames for it. (You
+ // shouldn't generally be giving this method content without a document
+ // anyway).
+ // Rebuilding the frame tree can have bad effects, especially if it's the
+ // frame tree for chrome (see bug 157322).
+ NS_ENSURE_TRUE(aContent->GetComposedDoc(), NS_ERROR_FAILURE);
+
+ // Is the frame ib-split? If so, we need to reframe the containing
+ // block *here*, rather than trying to remove and re-insert the
+ // content (which would otherwise result in *two* nested reframe
+ // containing block from ContentRemoved() and ContentInserted(),
+ // below!). We'd really like to optimize away one of those
+ // containing block reframes, hence the code here.
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame && frame->IsFrameOfType(nsIFrame::eMathML)) {
+ // Reframe the topmost MathML element to prevent exponential blowup
+ // (see bug 397518)
+ while (true) {
+ nsIContent* parentContent = aContent->GetParent();
+ nsIFrame* parentContentFrame = parentContent->GetPrimaryFrame();
+ if (!parentContentFrame || !parentContentFrame->IsFrameOfType(nsIFrame::eMathML))
+ break;
+ aContent = parentContent;
+ frame = parentContentFrame;
+ }
+ }
+
+ if (frame) {
+ nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(frame);
+ if (nonGeneratedAncestor->GetContent() != aContent) {
+ return RecreateFramesForContent(nonGeneratedAncestor->GetContent(), aAsyncInsert,
+ aFlags, aDestroyedFramesFor);
+ }
+
+ if (frame->GetStateBits() & NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT) {
+ // Recreate the frames for the entire nsIAnonymousContentCreator tree
+ // since |frame| or one of its descendants may need an nsStyleContext
+ // that associates it to a CSS pseudo-element, and only the
+ // nsIAnonymousContentCreator that created this content knows how to make
+ // that happen.
+ nsIAnonymousContentCreator* acc = nullptr;
+ nsIFrame* ancestor = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+ while (!(acc = do_QueryFrame(ancestor))) {
+ ancestor = nsLayoutUtils::GetParentOrPlaceholderFor(ancestor);
+ }
+ NS_ASSERTION(acc, "Where is the nsIAnonymousContentCreator? We may fail "
+ "to recreate its content correctly");
+ // nsSVGUseFrame is special, and we know this is unnecessary for it.
+ if (ancestor->GetType() != nsGkAtoms::svgUseFrame) {
+ NS_ASSERTION(aContent->IsInNativeAnonymousSubtree(),
+ "Why is NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT set?");
+ return RecreateFramesForContent(ancestor->GetContent(), aAsyncInsert,
+ aFlags, aDestroyedFramesFor);
+ }
+ }
+
+ nsIFrame* parent = frame->GetParent();
+ nsIContent* parentContent = parent ? parent->GetContent() : nullptr;
+ // If the parent frame is a leaf then the subsequent insert will fail to
+ // create a frame, so we need to recreate the parent content. This happens
+ // with native anonymous content from the editor.
+ if (parent && parent->IsLeaf() && parentContent &&
+ parentContent != aContent) {
+ return RecreateFramesForContent(parentContent, aAsyncInsert, aFlags,
+ aDestroyedFramesFor);
+ }
+ }
+
+ nsresult rv = NS_OK;
+ nsIContent* container;
+ if (frame && MaybeRecreateContainerForFrameRemoval(frame, aFlags, &rv,
+ &container)) {
+ MOZ_ASSERT(container);
+ if (aDestroyedFramesFor) {
+ *aDestroyedFramesFor = container;
+ }
+ return rv;
+ }
+
+ nsINode* containerNode = aContent->GetParentNode();
+ // XXXbz how can containerNode be null here?
+ if (containerNode) {
+ // Before removing the frames associated with the content object,
+ // ask them to save their state onto a temporary state object.
+ CaptureStateForFramesOf(aContent, mTempFrameTreeState);
+
+ // Need the nsIContent parent, which might be null here, since we need to
+ // pass it to ContentInserted and ContentRemoved.
+ nsCOMPtr<nsIContent> container = aContent->GetParent();
+
+ // Remove the frames associated with the content object.
+ bool didReconstruct;
+ nsIContent* nextSibling = aContent->IsRootOfAnonymousSubtree() ?
+ nullptr : aContent->GetNextSibling();
+ const bool reconstruct = aFlags != REMOVE_DESTROY_FRAMES;
+ RemoveFlags flags = reconstruct ? REMOVE_FOR_RECONSTRUCTION : aFlags;
+ rv = ContentRemoved(container, aContent, nextSibling, flags,
+ &didReconstruct, aDestroyedFramesFor);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (reconstruct && !didReconstruct) {
+ // Now, recreate the frames associated with this content object. If
+ // ContentRemoved triggered reconstruction, then we don't need to do this
+ // because the frames will already have been built.
+ if (aAsyncInsert) {
+ // XXXmats doesn't frame state need to be restored in this case too?
+ RestyleManager()->PostRestyleEvent(
+ aContent->AsElement(), nsRestyleHint(0), nsChangeHint_ReconstructFrame);
+ } else {
+ rv = ContentInserted(container, aContent, mTempFrameTreeState, false);
+ }
+ }
+ }
+
+ return rv;
+}
+
+void
+nsCSSFrameConstructor::DestroyFramesFor(nsIContent* aContent,
+ nsIContent** aDestroyedFramesFor)
+{
+ MOZ_ASSERT(aContent && aContent->GetParentNode());
+
+ bool didReconstruct;
+ nsIContent* nextSibling =
+ aContent->IsRootOfAnonymousSubtree() ? nullptr : aContent->GetNextSibling();
+ ContentRemoved(aContent->GetParent(), aContent, nextSibling,
+ REMOVE_DESTROY_FRAMES, &didReconstruct, aDestroyedFramesFor);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+// Block frame construction code
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::GetFirstLetterStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (aContent) {
+ return mPresShell->StyleSet()->
+ ResolvePseudoElementStyle(aContent->AsElement(),
+ CSSPseudoElementType::firstLetter,
+ aStyleContext,
+ nullptr);
+ }
+ return nullptr;
+}
+
+already_AddRefed<nsStyleContext>
+nsCSSFrameConstructor::GetFirstLineStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (aContent) {
+ return mPresShell->StyleSet()->
+ ResolvePseudoElementStyle(aContent->AsElement(),
+ CSSPseudoElementType::firstLine,
+ aStyleContext,
+ nullptr);
+ }
+ return nullptr;
+}
+
+// Predicate to see if a given content (block element) has
+// first-letter style applied to it.
+bool
+nsCSSFrameConstructor::ShouldHaveFirstLetterStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ return nsLayoutUtils::HasPseudoStyle(aContent, aStyleContext,
+ CSSPseudoElementType::firstLetter,
+ mPresShell->GetPresContext());
+}
+
+bool
+nsCSSFrameConstructor::HasFirstLetterStyle(nsIFrame* aBlockFrame)
+{
+ NS_PRECONDITION(aBlockFrame, "Need a frame");
+ NS_ASSERTION(nsLayoutUtils::GetAsBlock(aBlockFrame),
+ "Not a block frame?");
+
+ return (aBlockFrame->GetStateBits() & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0;
+}
+
+bool
+nsCSSFrameConstructor::ShouldHaveFirstLineStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ bool hasFirstLine =
+ nsLayoutUtils::HasPseudoStyle(aContent, aStyleContext,
+ CSSPseudoElementType::firstLine,
+ mPresShell->GetPresContext());
+ if (hasFirstLine) {
+ // But disable for fieldsets
+ int32_t namespaceID;
+ nsIAtom* tag = mDocument->BindingManager()->ResolveTag(aContent,
+ &namespaceID);
+ // This check must match the one in FindHTMLData.
+ hasFirstLine = tag != nsGkAtoms::fieldset ||
+ namespaceID != kNameSpaceID_XHTML;
+ }
+
+ return hasFirstLine;
+}
+
+void
+nsCSSFrameConstructor::ShouldHaveSpecialBlockStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool* aHaveFirstLetterStyle,
+ bool* aHaveFirstLineStyle)
+{
+ *aHaveFirstLetterStyle =
+ ShouldHaveFirstLetterStyle(aContent, aStyleContext);
+ *aHaveFirstLineStyle =
+ ShouldHaveFirstLineStyle(aContent, aStyleContext);
+}
+
+/* static */
+const nsCSSFrameConstructor::PseudoParentData
+nsCSSFrameConstructor::sPseudoParentData[eParentTypeCount] = {
+ { // Cell
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET |
+ FCDATA_USE_CHILD_ITEMS |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRow),
+ &nsCSSFrameConstructor::ConstructTableCell),
+ &nsCSSAnonBoxes::tableCell
+ },
+ { // Row
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET |
+ FCDATA_USE_CHILD_ITEMS |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRowGroup),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup),
+ &nsCSSAnonBoxes::tableRow
+ },
+ { // Row group
+ FULL_CTOR_FCDATA(FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET |
+ FCDATA_USE_CHILD_ITEMS |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup),
+ &nsCSSAnonBoxes::tableRowGroup
+ },
+ { // Column group
+ FCDATA_DECL(FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET |
+ FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_SKIP_ABSPOS_PUSH |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable),
+ NS_NewTableColGroupFrame),
+ &nsCSSAnonBoxes::tableColGroup
+ },
+ { // Table
+ FULL_CTOR_FCDATA(FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS,
+ &nsCSSFrameConstructor::ConstructTable),
+ &nsCSSAnonBoxes::table
+ },
+ { // Ruby
+ FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_USE_CHILD_ITEMS |
+ FCDATA_SKIP_FRAMESET,
+ NS_NewRubyFrame),
+ &nsCSSAnonBoxes::ruby
+ },
+ { // Ruby Base
+ FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer) |
+ FCDATA_SKIP_FRAMESET,
+ NS_NewRubyBaseFrame),
+ &nsCSSAnonBoxes::rubyBase
+ },
+ { // Ruby Base Container
+ FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby) |
+ FCDATA_SKIP_FRAMESET,
+ NS_NewRubyBaseContainerFrame),
+ &nsCSSAnonBoxes::rubyBaseContainer
+ },
+ { // Ruby Text
+ FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyTextContainer) |
+ FCDATA_SKIP_FRAMESET,
+ NS_NewRubyTextFrame),
+ &nsCSSAnonBoxes::rubyText
+ },
+ { // Ruby Text Container
+ FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby) |
+ FCDATA_SKIP_FRAMESET,
+ NS_NewRubyTextContainerFrame),
+ &nsCSSAnonBoxes::rubyTextContainer
+ }
+};
+
+void
+nsCSSFrameConstructor::CreateNeededAnonFlexOrGridItems(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame)
+{
+ if (aItems.IsEmpty()) {
+ return;
+ }
+ const nsIAtom* parentType = aParentFrame->GetType();
+ if (parentType != nsGkAtoms::flexContainerFrame &&
+ parentType != nsGkAtoms::gridContainerFrame) {
+ return;
+ }
+
+ const bool isWebkitBox = IsFlexContainerForLegacyBox(aParentFrame,
+ parentType);
+ FCItemIterator iter(aItems);
+ do {
+ // Advance iter past children that don't want to be wrapped
+ if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState, isWebkitBox)) {
+ // Hit the end of the items without finding any remaining children that
+ // need to be wrapped. We're finished!
+ return;
+ }
+
+ // If our next potentially-wrappable child is whitespace, then see if
+ // there's anything wrappable immediately after it. If not, we just drop
+ // the whitespace and move on. (We're not supposed to create any anonymous
+ // flex/grid items that _only_ contain whitespace).
+ // (BUT if this is generated content, then we don't give whitespace nodes
+ // any special treatment, because they're probably not really whitespace --
+ // they're just temporarily empty, waiting for their generated text.)
+ // XXXdholbert If this node's generated text will *actually end up being
+ // entirely whitespace*, then we technically should still skip over it, per
+ // the CSS grid & flexbox specs. I'm not bothering with that at this point,
+ // since it's a pretty extreme edge case.
+ if (!aParentFrame->IsGeneratedContentFrame() &&
+ iter.item().IsWhitespace(aState)) {
+ FCItemIterator afterWhitespaceIter(iter);
+ bool hitEnd = afterWhitespaceIter.SkipWhitespace(aState);
+ bool nextChildNeedsAnonItem =
+ !hitEnd &&
+ afterWhitespaceIter.item().NeedsAnonFlexOrGridItem(aState, isWebkitBox);
+
+ if (!nextChildNeedsAnonItem) {
+ // There's nothing after the whitespace that we need to wrap, so we
+ // just drop this run of whitespace.
+ iter.DeleteItemsTo(afterWhitespaceIter);
+ if (hitEnd) {
+ // Nothing left to do -- we're finished!
+ return;
+ }
+ // else, we have a next child and it does not want to be wrapped. So,
+ // we jump back to the beginning of the loop to skip over that child
+ // (and anything else non-wrappable after it)
+ MOZ_ASSERT(!iter.IsDone() &&
+ !iter.item().NeedsAnonFlexOrGridItem(aState, isWebkitBox),
+ "hitEnd and/or nextChildNeedsAnonItem lied");
+ continue;
+ }
+ }
+
+ // Now |iter| points to the first child that needs to be wrapped in an
+ // anonymous flex/grid item. Now we see how many children after it also want
+ // to be wrapped in an anonymous flex/grid item.
+ FCItemIterator endIter(iter); // iterator to find the end of the group
+ endIter.SkipItemsThatNeedAnonFlexOrGridItem(aState, isWebkitBox);
+
+ NS_ASSERTION(iter != endIter,
+ "Should've had at least one wrappable child to seek past");
+
+ // Now, we create the anonymous flex or grid item to contain the children
+ // between |iter| and |endIter|.
+ nsIAtom* pseudoType = (aParentFrame->GetType() == nsGkAtoms::flexContainerFrame) ?
+ nsCSSAnonBoxes::anonymousFlexItem : nsCSSAnonBoxes::anonymousGridItem;
+ nsStyleContext* parentStyle = aParentFrame->StyleContext();
+ nsIContent* parentContent = aParentFrame->GetContent();
+ already_AddRefed<nsStyleContext> wrapperStyle =
+ mPresShell->StyleSet()->ResolveAnonymousBoxStyle(pseudoType, parentStyle);
+
+ static const FrameConstructionData sBlockFormattingContextFCData =
+ FCDATA_DECL(FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS,
+ NS_NewBlockFormattingContext);
+
+ FrameConstructionItem* newItem =
+ new FrameConstructionItem(&sBlockFormattingContextFCData,
+ // Use the content of our parent frame
+ parentContent,
+ // Lie about the tag; it doesn't matter anyway
+ pseudoType,
+ iter.item().mNameSpaceID,
+ // no pending binding
+ nullptr,
+ wrapperStyle,
+ true, nullptr);
+
+ newItem->mIsAllInline = newItem->mHasInlineEnds =
+ newItem->mStyleContext->StyleDisplay()->IsInlineOutsideStyle();
+ newItem->mIsBlock = !newItem->mIsAllInline;
+
+ MOZ_ASSERT(!newItem->mIsAllInline && newItem->mIsBlock,
+ "expecting anonymous flex/grid items to be block-level "
+ "(this will make a difference when we encounter "
+ "'align-items: baseline')");
+
+ // Anonymous flex and grid items induce line boundaries around their
+ // contents.
+ newItem->mChildItems.SetLineBoundaryAtStart(true);
+ newItem->mChildItems.SetLineBoundaryAtEnd(true);
+ // The parent of the items in aItems is also the parent of the items
+ // in mChildItems
+ newItem->mChildItems.SetParentHasNoXBLChildren(
+ aItems.ParentHasNoXBLChildren());
+
+ // Eat up all items between |iter| and |endIter| and put them in our
+ // wrapper. This advances |iter| to point to |endIter|.
+ iter.AppendItemsToList(endIter, newItem->mChildItems);
+
+ iter.InsertItem(newItem);
+ } while (!iter.IsDone());
+}
+
+/* static */ nsCSSFrameConstructor::RubyWhitespaceType
+nsCSSFrameConstructor::ComputeRubyWhitespaceType(StyleDisplay aPrevDisplay,
+ StyleDisplay aNextDisplay)
+{
+ MOZ_ASSERT(nsStyleDisplay::IsRubyDisplayType(aPrevDisplay) &&
+ nsStyleDisplay::IsRubyDisplayType(aNextDisplay));
+ if (aPrevDisplay == aNextDisplay &&
+ (aPrevDisplay == StyleDisplay::RubyBase ||
+ aPrevDisplay == StyleDisplay::RubyText)) {
+ return eRubyInterLeafWhitespace;
+ }
+ if (aNextDisplay == StyleDisplay::RubyText ||
+ aNextDisplay == StyleDisplay::RubyTextContainer) {
+ return eRubyInterLevelWhitespace;
+ }
+ return eRubyInterSegmentWhitespace;
+}
+
+/**
+ * This function checks the content from |aStartIter| to |aEndIter|,
+ * determines whether it contains only whitespace, and if yes,
+ * interprets the type of whitespace. This method does not change
+ * any of the iters.
+ */
+/* static */ nsCSSFrameConstructor::RubyWhitespaceType
+nsCSSFrameConstructor::InterpretRubyWhitespace(nsFrameConstructorState& aState,
+ const FCItemIterator& aStartIter,
+ const FCItemIterator& aEndIter)
+{
+ if (!aStartIter.item().IsWhitespace(aState)) {
+ return eRubyNotWhitespace;
+ }
+
+ FCItemIterator spaceEndIter(aStartIter);
+ spaceEndIter.SkipWhitespace(aState);
+ if (spaceEndIter != aEndIter) {
+ return eRubyNotWhitespace;
+ }
+
+ // Any leading or trailing whitespace in non-pseudo ruby box
+ // should have been trimmed, hence there should not be any
+ // whitespace at the start or the end.
+ MOZ_ASSERT(!aStartIter.AtStart() && !aEndIter.IsDone());
+ FCItemIterator prevIter(aStartIter);
+ prevIter.Prev();
+ return ComputeRubyWhitespaceType(
+ prevIter.item().mStyleContext->StyleDisplay()->mDisplay,
+ aEndIter.item().mStyleContext->StyleDisplay()->mDisplay);
+}
+
+
+/**
+ * This function eats up consecutive items which do not want the current
+ * parent into either a ruby base box or a ruby text box. When it
+ * returns, |aIter| points to the first item it doesn't wrap.
+ */
+void
+nsCSSFrameConstructor::WrapItemsInPseudoRubyLeafBox(
+ FCItemIterator& aIter,
+ nsStyleContext* aParentStyle, nsIContent* aParentContent)
+{
+ StyleDisplay parentDisplay = aParentStyle->StyleDisplay()->mDisplay;
+ ParentType parentType, wrapperType;
+ if (parentDisplay == StyleDisplay::RubyTextContainer) {
+ parentType = eTypeRubyTextContainer;
+ wrapperType = eTypeRubyText;
+ } else {
+ MOZ_ASSERT(parentDisplay == StyleDisplay::RubyBaseContainer);
+ parentType = eTypeRubyBaseContainer;
+ wrapperType = eTypeRubyBase;
+ }
+
+ MOZ_ASSERT(aIter.item().DesiredParentType() != parentType,
+ "Should point to something needs to be wrapped.");
+
+ FCItemIterator endIter(aIter);
+ endIter.SkipItemsNotWantingParentType(parentType);
+
+ WrapItemsInPseudoParent(aParentContent, aParentStyle,
+ wrapperType, aIter, endIter);
+}
+
+/**
+ * This function eats up consecutive items into a ruby level container.
+ * It may create zero or one level container. When it returns, |aIter|
+ * points to the first item it doesn't wrap.
+ */
+void
+nsCSSFrameConstructor::WrapItemsInPseudoRubyLevelContainer(
+ nsFrameConstructorState& aState, FCItemIterator& aIter,
+ nsStyleContext* aParentStyle, nsIContent* aParentContent)
+{
+ MOZ_ASSERT(aIter.item().DesiredParentType() != eTypeRuby,
+ "Pointing to a level container?");
+
+ FrameConstructionItem& firstItem = aIter.item();
+ ParentType wrapperType = firstItem.DesiredParentType();
+ if (wrapperType != eTypeRubyTextContainer) {
+ // If the first item is not ruby text,
+ // it should be in a base container.
+ wrapperType = eTypeRubyBaseContainer;
+ }
+
+ FCItemIterator endIter(aIter);
+ do {
+ if (endIter.SkipItemsWantingParentType(wrapperType) ||
+ // If the skipping above stops at some item which wants a
+ // different ruby parent, then we have finished.
+ IsRubyParentType(endIter.item().DesiredParentType())) {
+ // No more items need to be wrapped in this level container.
+ break;
+ }
+
+ FCItemIterator contentEndIter(endIter);
+ contentEndIter.SkipItemsNotWantingRubyParent();
+ // endIter must be on something doesn't want a ruby parent.
+ MOZ_ASSERT(contentEndIter != endIter);
+
+ // InterpretRubyWhitespace depends on the fact that any leading or
+ // trailing whitespace described in the spec have been trimmed at
+ // this point. With this precondition, it is safe not to check
+ // whether contentEndIter has been done.
+ RubyWhitespaceType whitespaceType =
+ InterpretRubyWhitespace(aState, endIter, contentEndIter);
+ if (whitespaceType == eRubyInterLevelWhitespace) {
+ // Remove inter-level whitespace.
+ bool atStart = (aIter == endIter);
+ endIter.DeleteItemsTo(contentEndIter);
+ if (atStart) {
+ aIter = endIter;
+ }
+ } else if (whitespaceType == eRubyInterSegmentWhitespace) {
+ // If this level container starts with inter-segment whitespaces,
+ // wrap them. Break at contentEndIter. Otherwise, leave it here.
+ // Break at endIter. They will be wrapped when we are here again.
+ if (aIter == endIter) {
+ MOZ_ASSERT(wrapperType == eTypeRubyBaseContainer,
+ "Inter-segment whitespace should be wrapped in rbc");
+ endIter = contentEndIter;
+ }
+ break;
+ } else if (wrapperType == eTypeRubyTextContainer &&
+ whitespaceType != eRubyInterLeafWhitespace) {
+ // Misparented inline content that's not inter-annotation
+ // whitespace doesn't belong in a pseudo ruby text container.
+ // Break at endIter.
+ break;
+ } else {
+ endIter = contentEndIter;
+ }
+ } while (!endIter.IsDone());
+
+ // It is possible that everything our parent wants us to wrap is
+ // simply an inter-level whitespace, which has been trimmed, or
+ // an inter-segment whitespace, which will be wrapped later.
+ // In those cases, don't create anything.
+ if (aIter != endIter) {
+ WrapItemsInPseudoParent(aParentContent, aParentStyle,
+ wrapperType, aIter, endIter);
+ }
+}
+
+/**
+ * This function trims leading and trailing whitespaces
+ * in the given item list.
+ */
+void
+nsCSSFrameConstructor::TrimLeadingAndTrailingWhitespaces(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems)
+{
+ FCItemIterator iter(aItems);
+ if (!iter.IsDone() &&
+ iter.item().IsWhitespace(aState)) {
+ FCItemIterator spaceEndIter(iter);
+ spaceEndIter.SkipWhitespace(aState);
+ iter.DeleteItemsTo(spaceEndIter);
+ }
+
+ iter.SetToEnd();
+ if (!iter.AtStart()) {
+ FCItemIterator spaceEndIter(iter);
+ do {
+ iter.Prev();
+ if (iter.AtStart()) {
+ // It's fine to not check the first item, because we
+ // should have trimmed leading whitespaces above.
+ break;
+ }
+ } while (iter.item().IsWhitespace(aState));
+ iter.Next();
+ if (iter != spaceEndIter) {
+ iter.DeleteItemsTo(spaceEndIter);
+ }
+ }
+}
+
+/**
+ * This function walks through the child list (aItems) and creates
+ * needed pseudo ruby boxes to wrap misparented children.
+ */
+void
+nsCSSFrameConstructor::CreateNeededPseudoInternalRubyBoxes(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame)
+{
+ const ParentType ourParentType = GetParentType(aParentFrame);
+ if (!IsRubyParentType(ourParentType) ||
+ aItems.AllWantParentType(ourParentType)) {
+ return;
+ }
+
+ if (!IsRubyPseudo(aParentFrame)) {
+ // Normally, ruby pseudo frames start from and end at some elements,
+ // which means they don't have leading and trailing whitespaces at
+ // all. But there are two cases where they do actually have leading
+ // or trailing whitespaces:
+ // 1. It is an inter-segment whitespace which in an individual ruby
+ // base container.
+ // 2. The pseudo frame starts from or ends at consecutive inline
+ // content, which is not pure whitespace, but includes some.
+ // In either case, the whitespaces are not the leading or trailing
+ // whitespaces defined in the spec, and thus should not be trimmed.
+ TrimLeadingAndTrailingWhitespaces(aState, aItems);
+ }
+
+ FCItemIterator iter(aItems);
+ nsIContent* parentContent = aParentFrame->GetContent();
+ nsStyleContext* parentStyle = aParentFrame->StyleContext();
+ while (!iter.IsDone()) {
+ if (!iter.SkipItemsWantingParentType(ourParentType)) {
+ if (ourParentType == eTypeRuby) {
+ WrapItemsInPseudoRubyLevelContainer(aState, iter, parentStyle,
+ parentContent);
+ } else {
+ WrapItemsInPseudoRubyLeafBox(iter, parentStyle, parentContent);
+ }
+ }
+ }
+}
+
+/*
+ * This function works as follows: we walk through the child list (aItems) and
+ * find items that cannot have aParentFrame as their parent. We wrap
+ * continuous runs of such items into a FrameConstructionItem for a frame that
+ * gets them closer to their desired parents. For example, a run of non-row
+ * children of a row-group will get wrapped in a row. When we later construct
+ * the frame for this wrapper (in this case for the row), it'll be the correct
+ * parent for the cells in the set of items we wrapped or we'll wrap cells
+ * around everything else. At the end of this method, aItems is guaranteed to
+ * contain only items for frames that can be direct kids of aParentFrame.
+ */
+void
+nsCSSFrameConstructor::CreateNeededPseudoContainers(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame)
+{
+ ParentType ourParentType = GetParentType(aParentFrame);
+ if (IsRubyParentType(ourParentType) ||
+ aItems.AllWantParentType(ourParentType)) {
+ // Nothing to do here
+ return;
+ }
+
+ FCItemIterator iter(aItems);
+ do {
+ if (iter.SkipItemsWantingParentType(ourParentType)) {
+ // Nothing else to do here; we're finished
+ return;
+ }
+
+ // Now we're pointing to the first child that wants a different parent
+ // type.
+
+ // Now try to figure out what kids we can group together. We can generally
+ // group everything that has a different desired parent type from us. Two
+ // exceptions to this:
+ // 1) If our parent type is table, we can't group columns with anything
+ // else other than whitespace.
+ // 2) Whitespace that lies between two things we can group which both want
+ // a non-block parent should be dropped, even if we can't group them
+ // with each other and even if the whitespace wants a parent of
+ // ourParentType. Ends of the list count as things that don't want a
+ // block parent (so that for example we'll drop a whitespace-only list).
+
+ FCItemIterator endIter(iter); /* iterator to find the end of the group */
+ ParentType groupingParentType = endIter.item().DesiredParentType();
+ if (aItems.AllWantParentType(groupingParentType) &&
+ groupingParentType != eTypeBlock) {
+ // Just group them all and be done with it. We need the check for
+ // eTypeBlock here to catch the "all the items are whitespace" case
+ // described above.
+ endIter.SetToEnd();
+ } else {
+ // Locate the end of the group.
+
+ // Keep track of the type the previous item wanted, in case we have to
+ // deal with whitespace. Start it off with ourParentType, since that's
+ // the last thing |iter| would have skipped over.
+ ParentType prevParentType = ourParentType;
+ do {
+ // Walk an iterator past any whitespace that we might be able to drop
+ // from the list
+ FCItemIterator spaceEndIter(endIter);
+ if (prevParentType != eTypeBlock &&
+ !aParentFrame->IsGeneratedContentFrame() &&
+ spaceEndIter.item().IsWhitespace(aState)) {
+ bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
+
+ // We drop the whitespace in the following cases:
+ // 1) If these are not trailing spaces and the next item wants a table
+ // or table-part parent
+ // 2) If these are trailing spaces and aParentFrame is a
+ // tabular container according to rule 1.3 of CSS 2.1 Sec 17.2.1.
+ // (Being a tabular container pretty much means ourParentType is
+ // not eTypeBlock besides the eTypeColGroup case, which won't
+ // reach here.)
+ if ((!trailingSpaces &&
+ IsTableParentType(spaceEndIter.item().DesiredParentType())) ||
+ (trailingSpaces && ourParentType != eTypeBlock)) {
+ bool updateStart = (iter == endIter);
+ endIter.DeleteItemsTo(spaceEndIter);
+ NS_ASSERTION(trailingSpaces == endIter.IsDone(),
+ "These should match");
+
+ if (updateStart) {
+ iter = endIter;
+ }
+
+ if (trailingSpaces) {
+ break; /* Found group end */
+ }
+
+ if (updateStart) {
+ // Update groupingParentType, since it might have been eTypeBlock
+ // just because of the whitespace.
+ groupingParentType = iter.item().DesiredParentType();
+ }
+ }
+ }
+
+ // Now endIter points to a non-whitespace item or a non-droppable
+ // whitespace item. In the latter case, if this is the end of the group
+ // we'll traverse this whitespace again. But it'll all just be quick
+ // DesiredParentType() checks which will match ourParentType (that's
+ // what it means that this is the group end), so it's OK.
+ // However, when we are grouping a ruby parent, and endIter points to
+ // a non-droppable whitespace, if the next non-whitespace item also
+ // wants a ruby parent, the whitespace should also be included into
+ // the current ruby container.
+ prevParentType = endIter.item().DesiredParentType();
+ if (prevParentType == ourParentType &&
+ (endIter == spaceEndIter ||
+ spaceEndIter.IsDone() ||
+ !IsRubyParentType(groupingParentType) ||
+ !IsRubyParentType(spaceEndIter.item().DesiredParentType()))) {
+ // End the group at endIter.
+ break;
+ }
+
+ if (ourParentType == eTypeTable &&
+ (prevParentType == eTypeColGroup) !=
+ (groupingParentType == eTypeColGroup)) {
+ // Either we started with columns and now found something else, or vice
+ // versa. In any case, end the grouping.
+ break;
+ }
+
+ // If we have some whitespace that we were not able to drop and there is
+ // an item after the whitespace that is already properly parented, then
+ // make sure to include the spaces in our group but stop the group after
+ // that.
+ if (spaceEndIter != endIter &&
+ !spaceEndIter.IsDone() &&
+ ourParentType == spaceEndIter.item().DesiredParentType()) {
+ endIter = spaceEndIter;
+ break;
+ }
+
+ // Include the whitespace we didn't drop (if any) in the group.
+ endIter = spaceEndIter;
+ prevParentType = endIter.item().DesiredParentType();
+
+ endIter.Next();
+ } while (!endIter.IsDone());
+ }
+
+ if (iter == endIter) {
+ // Nothing to wrap here; just skipped some whitespace
+ continue;
+ }
+
+ // Now group together all the items between iter and endIter. The right
+ // parent type to use depends on ourParentType.
+ ParentType wrapperType;
+ switch (ourParentType) {
+ case eTypeRow:
+ // The parent type for a cell is eTypeBlock, since that's what a cell
+ // looks like to its kids.
+ wrapperType = eTypeBlock;
+ break;
+ case eTypeRowGroup:
+ wrapperType = eTypeRow;
+ break;
+ case eTypeTable:
+ // Either colgroup or rowgroup, depending on what we're grouping.
+ wrapperType = groupingParentType == eTypeColGroup ?
+ eTypeColGroup : eTypeRowGroup;
+ break;
+ case eTypeColGroup:
+ MOZ_CRASH("Colgroups should be suppresing non-col child items");
+ default:
+ NS_ASSERTION(ourParentType == eTypeBlock, "Unrecognized parent type");
+ if (IsRubyParentType(groupingParentType)) {
+ wrapperType = eTypeRuby;
+ } else {
+ NS_ASSERTION(IsTableParentType(groupingParentType),
+ "groupingParentType should be either Ruby or table");
+ wrapperType = eTypeTable;
+ }
+ }
+
+ nsStyleContext* parentStyle = aParentFrame->StyleContext();
+ WrapItemsInPseudoParent(aParentFrame->GetContent(), parentStyle,
+ wrapperType, iter, endIter);
+
+ // Now |iter| points to the item that was the first one we didn't wrap;
+ // loop and see whether we need to skip it or wrap it in something
+ // different.
+ } while (!iter.IsDone());
+}
+
+/**
+ * This method wraps frame construction item from |aIter| to
+ * |aEndIter|. After it returns, aIter points to the first item
+ * after the wrapper.
+ */
+void
+nsCSSFrameConstructor::WrapItemsInPseudoParent(nsIContent* aParentContent,
+ nsStyleContext* aParentStyle,
+ ParentType aWrapperType,
+ FCItemIterator& aIter,
+ const FCItemIterator& aEndIter)
+{
+ const PseudoParentData& pseudoData = sPseudoParentData[aWrapperType];
+ nsIAtom* pseudoType = *pseudoData.mPseudoType;
+ StyleDisplay parentDisplay = aParentStyle->StyleDisplay()->mDisplay;
+
+ if (pseudoType == nsCSSAnonBoxes::table &&
+ (parentDisplay == StyleDisplay::Inline ||
+ parentDisplay == StyleDisplay::RubyBase ||
+ parentDisplay == StyleDisplay::RubyText)) {
+ pseudoType = nsCSSAnonBoxes::inlineTable;
+ }
+
+ already_AddRefed<nsStyleContext> wrapperStyle =
+ mPresShell->StyleSet()->ResolveAnonymousBoxStyle(pseudoType, aParentStyle);
+ FrameConstructionItem* newItem =
+ new FrameConstructionItem(&pseudoData.mFCData,
+ // Use the content of our parent frame
+ aParentContent,
+ // Lie about the tag; it doesn't matter anyway
+ pseudoType,
+ // The namespace does matter, however; it needs
+ // to match that of our first child item to
+ // match the old behavior
+ aIter.item().mNameSpaceID,
+ // no pending binding
+ nullptr,
+ wrapperStyle,
+ true, nullptr);
+
+ const nsStyleDisplay* disp = newItem->mStyleContext->StyleDisplay();
+ // Here we're cheating a tad... technically, table-internal items should be
+ // inline if aParentFrame is inline, but they'll get wrapped in an
+ // inline-table in the end, so it'll all work out. In any case, arguably
+ // we don't need to maintain this state at this point... but it's better
+ // to, I guess.
+ newItem->mIsAllInline = newItem->mHasInlineEnds =
+ disp->IsInlineOutsideStyle();
+
+ bool isRuby = disp->IsRubyDisplayType();
+ // All types of ruby frames need a block frame to provide line layout,
+ // hence they are always line participant.
+ newItem->mIsLineParticipant = isRuby;
+
+ if (!isRuby) {
+ // Table pseudo frames always induce line boundaries around their
+ // contents.
+ newItem->mChildItems.SetLineBoundaryAtStart(true);
+ newItem->mChildItems.SetLineBoundaryAtEnd(true);
+ }
+ // The parent of the items in aItems is also the parent of the items
+ // in mChildItems
+ newItem->mChildItems.SetParentHasNoXBLChildren(
+ aIter.List()->ParentHasNoXBLChildren());
+
+ // Eat up all items between |aIter| and |aEndIter| and put them in our
+ // wrapper Advances |aIter| to point to |aEndIter|.
+ aIter.AppendItemsToList(aEndIter, newItem->mChildItems);
+
+ aIter.InsertItem(newItem);
+}
+
+void nsCSSFrameConstructor::CreateNeededPseudoSiblings(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame)
+{
+ if (aItems.IsEmpty() ||
+ GetParentType(aParentFrame) != eTypeRuby) {
+ return;
+ }
+
+ FCItemIterator iter(aItems);
+ StyleDisplay firstDisplay = iter.item().mStyleContext->StyleDisplay()->mDisplay;
+ if (firstDisplay == StyleDisplay::RubyBaseContainer) {
+ return;
+ }
+ NS_ASSERTION(firstDisplay == StyleDisplay::RubyTextContainer,
+ "Child of ruby frame should either a rbc or a rtc");
+
+ const PseudoParentData& pseudoData =
+ sPseudoParentData[eTypeRubyBaseContainer];
+ already_AddRefed<nsStyleContext> pseudoStyle = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(*pseudoData.mPseudoType,
+ aParentFrame->StyleContext());
+ FrameConstructionItem* newItem =
+ new FrameConstructionItem(&pseudoData.mFCData,
+ // Use the content of the parent frame
+ aParentFrame->GetContent(),
+ // Tag type
+ *pseudoData.mPseudoType,
+ // Use the namespace of the rtc frame
+ iter.item().mNameSpaceID,
+ // no pending binding
+ nullptr,
+ pseudoStyle,
+ true, nullptr);
+ newItem->mIsAllInline = true;
+ newItem->mChildItems.SetParentHasNoXBLChildren(true);
+ iter.InsertItem(newItem);
+}
+
+#ifdef DEBUG
+/**
+ * Returns true iff aFrame should be wrapped in an anonymous flex/grid item,
+ * rather than being a direct child of aContainerFrame.
+ *
+ * NOTE: aContainerFrame must be a flex or grid container - this function is
+ * purely for sanity-checking the children of these container types.
+ * NOTE: See also NeedsAnonFlexOrGridItem(), for the non-debug version of this
+ * logic (which operates a bit earlier, on FCData instead of frames).
+ */
+static bool
+FrameWantsToBeInAnonymousItem(const nsIFrame* aContainerFrame,
+ const nsIFrame* aFrame)
+{
+ nsIAtom* containerType = aContainerFrame->GetType();
+ MOZ_ASSERT(containerType == nsGkAtoms::flexContainerFrame ||
+ containerType == nsGkAtoms::gridContainerFrame);
+
+ // Any line-participant frames (e.g. text) definitely want to be wrapped in
+ // an anonymous flex/grid item.
+ if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ return true;
+ }
+
+ // If the container is a -webkit-box/-webkit-inline-box, then placeholders
+ // also need to be wrapped, for compatibility.
+ if (containerType == nsGkAtoms::flexContainerFrame &&
+ aContainerFrame->HasAnyStateBits(NS_STATE_FLEX_IS_LEGACY_WEBKIT_BOX) &&
+ aFrame->GetType() == nsGkAtoms::placeholderFrame) {
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+static void
+VerifyGridFlexContainerChildren(nsIFrame* aParentFrame,
+ const nsFrameList& aChildren)
+{
+#ifdef DEBUG
+ auto parentType = aParentFrame->GetType();
+ if (parentType != nsGkAtoms::flexContainerFrame &&
+ parentType != nsGkAtoms::gridContainerFrame) {
+ return;
+ }
+
+ bool prevChildWasAnonItem = false;
+ for (const nsIFrame* child : aChildren) {
+ MOZ_ASSERT(!FrameWantsToBeInAnonymousItem(aParentFrame, child),
+ "frame wants to be inside an anonymous item, but it isn't");
+ if (IsAnonymousFlexOrGridItem(child)) {
+ AssertAnonymousFlexOrGridItemParent(child, aParentFrame);
+ MOZ_ASSERT(!prevChildWasAnonItem, "two anon items in a row");
+ nsIFrame* firstWrappedChild = child->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(firstWrappedChild, "anonymous item shouldn't be empty");
+ prevChildWasAnonItem = true;
+ } else {
+ prevChildWasAnonItem = false;
+ }
+ }
+#endif
+}
+
+inline void
+nsCSSFrameConstructor::ConstructFramesFromItemList(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems)
+{
+ CreateNeededPseudoContainers(aState, aItems, aParentFrame);
+ CreateNeededAnonFlexOrGridItems(aState, aItems, aParentFrame);
+ CreateNeededPseudoInternalRubyBoxes(aState, aItems, aParentFrame);
+ CreateNeededPseudoSiblings(aState, aItems, aParentFrame);
+
+ aItems.SetTriedConstructingFrames();
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ NS_ASSERTION(iter.item().DesiredParentType() == GetParentType(aParentFrame),
+ "Needed pseudos didn't get created; expect bad things");
+ ConstructFramesFromItem(aState, iter, aParentFrame, aFrameItems);
+ }
+
+ VerifyGridFlexContainerChildren(aParentFrame, aFrameItems);
+ NS_ASSERTION(!aState.mHavePendingPopupgroup,
+ "Should have proccessed it by now");
+}
+
+void
+nsCSSFrameConstructor::AddFCItemsForAnonymousContent(
+ nsFrameConstructorState& aState,
+ nsContainerFrame* aFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
+ FrameConstructionItemList& aItemsToConstruct,
+ uint32_t aExtraFlags)
+{
+ for (uint32_t i = 0; i < aAnonymousItems.Length(); ++i) {
+ nsIContent* content = aAnonymousItems[i].mContent;
+#ifdef DEBUG
+ nsIAnonymousContentCreator* creator = do_QueryFrame(aFrame);
+ NS_ASSERTION(!creator || !creator->CreateFrameFor(content),
+ "If you need to use CreateFrameFor, you need to call "
+ "CreateAnonymousFrames manually and not follow the standard "
+ "ProcessChildren() codepath for this frame");
+#endif
+ // Gecko-styled nodes should have no pending restyle flags.
+ MOZ_ASSERT_IF(!content->IsStyledByServo(),
+ !content->IsElement() ||
+ !(content->GetFlags() & ELEMENT_ALL_RESTYLE_FLAGS));
+ // Assert some things about this content
+ MOZ_ASSERT(!(content->GetFlags() &
+ (NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME)),
+ "Should not be marked as needing frames");
+ MOZ_ASSERT(!content->GetPrimaryFrame(),
+ "Should have no existing frame");
+ MOZ_ASSERT(!content->IsNodeOfType(nsINode::eCOMMENT) &&
+ !content->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION),
+ "Why is someone creating garbage anonymous content");
+
+ RefPtr<nsStyleContext> styleContext;
+ TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
+ parentDisplayBasedStyleFixupSkipper(aState.mTreeMatchContext);
+ if (aAnonymousItems[i].mStyleContext) {
+ // If we have an explicit style context, that means that the anonymous
+ // content creator had its own plan for the style, and doesn't need the
+ // computed style obtained by cascading this content as a normal node.
+ // This happens when a native anonymous node is used to implement a
+ // pseudo-element. Allowing Servo to traverse these nodes would be wasted
+ // work, so assert that we didn't do that.
+ MOZ_ASSERT_IF(content->IsStyledByServo(), !content->HasServoData());
+ styleContext = aAnonymousItems[i].mStyleContext.forget();
+ } else {
+ // If we don't have an explicit style context, that means we need the
+ // ordinary computed values. Make sure we eagerly cascaded them when the
+ // anonymous nodes were created.
+ MOZ_ASSERT_IF(content->IsStyledByServo() && content->IsElement(),
+ content->HasServoData());
+ styleContext = ResolveStyleContext(aFrame, content, &aState);
+ }
+
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* anonChildren = nullptr;
+ if (!aAnonymousItems[i].mChildren.IsEmpty()) {
+ anonChildren = &aAnonymousItems[i].mChildren;
+ }
+
+ uint32_t flags = ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK |
+ ITEM_IS_ANONYMOUSCONTENTCREATOR_CONTENT | aExtraFlags;
+
+ AddFrameConstructionItemsInternal(aState, content, aFrame,
+ content->NodeInfo()->NameAtom(),
+ content->GetNameSpaceID(),
+ true, styleContext, flags,
+ anonChildren, aItemsToConstruct);
+ }
+}
+
+void
+nsCSSFrameConstructor::ProcessChildren(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame* aFrame,
+ const bool aCanHaveGeneratedContent,
+ nsFrameItems& aFrameItems,
+ const bool aAllowBlockStyles,
+ PendingBinding* aPendingBinding,
+ nsIFrame* aPossiblyLeafFrame)
+{
+ NS_PRECONDITION(aFrame, "Must have parent frame here");
+ NS_PRECONDITION(aFrame->GetContentInsertionFrame() == aFrame,
+ "Parent frame in ProcessChildren should be its own "
+ "content insertion frame");
+ const uint32_t kMaxDepth = 2 * MAX_REFLOW_DEPTH;
+ static_assert(kMaxDepth <= UINT16_MAX, "mCurrentDepth type is too narrow");
+ AutoRestore<uint16_t> savedDepth(mCurrentDepth);
+ if (mCurrentDepth != UINT16_MAX) {
+ ++mCurrentDepth;
+ }
+
+ if (!aPossiblyLeafFrame) {
+ aPossiblyLeafFrame = aFrame;
+ }
+
+ // XXXbz ideally, this would do all the pushing of various
+ // containing blocks as needed, so callers don't have to do it...
+
+ bool haveFirstLetterStyle = false, haveFirstLineStyle = false;
+ if (aAllowBlockStyles) {
+ ShouldHaveSpecialBlockStyle(aContent, aStyleContext, &haveFirstLetterStyle,
+ &haveFirstLineStyle);
+ }
+
+ // The logic here needs to match the logic in GetFloatContainingBlock()
+ nsFrameConstructorSaveState floatSaveState;
+ if (ShouldSuppressFloatingOfDescendants(aFrame)) {
+ aState.PushFloatContainingBlock(nullptr, floatSaveState);
+ } else if (aFrame->IsFloatContainingBlock()) {
+ aState.PushFloatContainingBlock(aFrame, floatSaveState);
+ }
+
+ nsFrameConstructorState::PendingBindingAutoPusher pusher(aState,
+ aPendingBinding);
+
+ FrameConstructionItemList itemsToConstruct;
+
+ // If we have first-letter or first-line style then frames can get
+ // moved around so don't set these flags.
+ if (aAllowBlockStyles && !haveFirstLetterStyle && !haveFirstLineStyle) {
+ itemsToConstruct.SetLineBoundaryAtStart(true);
+ itemsToConstruct.SetLineBoundaryAtEnd(true);
+ }
+
+ // Create any anonymous frames we need here. This must happen before the
+ // non-anonymous children are processed to ensure that popups are never
+ // constructed before the popupset.
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
+ GetAnonymousContent(aContent, aPossiblyLeafFrame, anonymousItems);
+#ifdef DEBUG
+ for (uint32_t i = 0; i < anonymousItems.Length(); ++i) {
+ MOZ_ASSERT(anonymousItems[i].mContent->IsRootOfAnonymousSubtree(),
+ "Content should know it's an anonymous subtree");
+ }
+#endif
+ AddFCItemsForAnonymousContent(aState, aFrame, anonymousItems,
+ itemsToConstruct);
+
+ if (!aPossiblyLeafFrame->IsLeaf()) {
+ // :before/:after content should have the same style context parent
+ // as normal kids.
+ // Note that we don't use this style context for looking up things like
+ // special block styles because in some cases involving table pseudo-frames
+ // it has nothing to do with the parent frame's desired behavior.
+ nsStyleContext* styleContext;
+
+ if (aCanHaveGeneratedContent) {
+ aFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT);
+ styleContext =
+ nsFrame::CorrectStyleParentFrame(aFrame, nullptr)->StyleContext();
+ // Probe for generated content before
+ CreateGeneratedContentItem(aState, aFrame, aContent, styleContext,
+ CSSPseudoElementType::before,
+ itemsToConstruct);
+ }
+
+ const bool addChildItems = MOZ_LIKELY(mCurrentDepth < kMaxDepth);
+ if (!addChildItems) {
+ NS_WARNING("ProcessChildren max depth exceeded");
+ }
+
+ InsertionPoint insertion(aFrame, nullptr);
+ FlattenedChildIterator iter(aContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ // Get the parent of the content and check if it is a XBL children element
+ // (if the content is a children element then parent != aContent because the
+ // FlattenedChildIterator will transitively iterate through <xbl:children>
+ // for default content). Push the children element as an ancestor here because
+ // it does not have a frame and would not otherwise be pushed as an ancestor.
+ insertion.mContainer = aContent;
+ nsIContent* parent = child->GetParent();
+ MOZ_ASSERT(parent, "Parent must be non-null because we are iterating children.");
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(aState.mTreeMatchContext);
+ if (parent != aContent && parent->IsElement()) {
+ insertion.mContainer = child->GetFlattenedTreeParent();
+ MOZ_ASSERT(insertion.mContainer == GetInsertionPoint(parent, child).mContainer);
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(parent->AsElement());
+ } else {
+ ancestorPusher.PushStyleScope(parent->AsElement());
+ }
+ }
+
+ // Frame construction item construction should not post
+ // restyles, so removing restyle flags here is safe.
+ child->UnsetRestyleFlagsIfGecko();
+ if (addChildItems) {
+ AddFrameConstructionItems(aState, child, iter.XBLInvolved(), insertion,
+ itemsToConstruct);
+ } else {
+ ClearLazyBits(child, child->GetNextSibling());
+ }
+ }
+ itemsToConstruct.SetParentHasNoXBLChildren(!iter.XBLInvolved());
+
+ if (aCanHaveGeneratedContent) {
+ // Probe for generated content after
+ CreateGeneratedContentItem(aState, aFrame, aContent, styleContext,
+ CSSPseudoElementType::after,
+ itemsToConstruct);
+ }
+ } else {
+ ClearLazyBits(aContent->GetFirstChild(), nullptr);
+ }
+
+ ConstructFramesFromItemList(aState, itemsToConstruct, aFrame, aFrameItems);
+
+ NS_ASSERTION(!aAllowBlockStyles || !aFrame->IsXULBoxFrame(),
+ "can't be both block and box");
+
+ if (haveFirstLetterStyle) {
+ WrapFramesInFirstLetterFrame(aFrame, aFrameItems);
+ }
+ if (haveFirstLineStyle) {
+ WrapFramesInFirstLineFrame(aState, aContent, aFrame, nullptr,
+ aFrameItems);
+ }
+
+ // We might end up with first-line frames that change
+ // AnyKidsNeedBlockParent() without changing itemsToConstruct, but that
+ // should never happen for cases whan aFrame->IsXULBoxFrame().
+ NS_ASSERTION(!haveFirstLineStyle || !aFrame->IsXULBoxFrame(),
+ "Shouldn't have first-line style if we're a box");
+ NS_ASSERTION(!aFrame->IsXULBoxFrame() ||
+ itemsToConstruct.AnyItemsNeedBlockParent() ==
+ (AnyKidsNeedBlockParent(aFrameItems.FirstChild()) != nullptr),
+ "Something went awry in our block parent calculations");
+
+ if (aFrame->IsXULBoxFrame() && itemsToConstruct.AnyItemsNeedBlockParent()) {
+ // XXXbz we could do this on the FrameConstructionItemList level,
+ // no? And if we cared we could look through the item list
+ // instead of groveling through the framelist here..
+ nsStyleContext *frameStyleContext = aFrame->StyleContext();
+ // Report a warning for non-GC frames, for chrome:
+ if (!aFrame->IsGeneratedContentFrame() &&
+ mPresShell->GetPresContext()->IsChrome()) {
+ nsIContent *badKid = AnyKidsNeedBlockParent(aFrameItems.FirstChild());
+ nsDependentAtomString parentTag(aContent->NodeInfo()->NameAtom()),
+ kidTag(badKid->NodeInfo()->NameAtom());
+ const char16_t* params[] = { parentTag.get(), kidTag.get() };
+ const nsStyleDisplay *display = frameStyleContext->StyleDisplay();
+ const char *message =
+ (display->mDisplay == StyleDisplay::InlineBox)
+ ? "NeededToWrapXULInlineBox" : "NeededToWrapXUL";
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Layout: FrameConstructor"),
+ mDocument,
+ nsContentUtils::eXUL_PROPERTIES,
+ message,
+ params, ArrayLength(params));
+ }
+
+ RefPtr<nsStyleContext> blockSC = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozXULAnonymousBlock,
+ frameStyleContext);
+ nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, blockSC);
+ // We might, in theory, want to set NS_BLOCK_FLOAT_MGR and
+ // NS_BLOCK_MARGIN_ROOT, but I think it's a bad idea given that
+ // a real block placed here wouldn't get those set on it.
+
+ InitAndRestoreFrame(aState, aContent, aFrame, blockFrame, false);
+
+ NS_ASSERTION(!blockFrame->HasView(), "need to do view reparenting");
+ ReparentFrames(this, blockFrame, aFrameItems);
+
+ blockFrame->SetInitialChildList(kPrincipalList, aFrameItems);
+ NS_ASSERTION(aFrameItems.IsEmpty(), "How did that happen?");
+ aFrameItems.Clear();
+ aFrameItems.AddChild(blockFrame);
+
+ aFrame->AddStateBits(NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK);
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Support for :first-line style
+
+// Special routine to handle placing a list of frames into a block
+// frame that has first-line style. The routine ensures that the first
+// collection of inline frames end up in a first-line frame.
+// NOTE: aState may have containing block information related to a
+// different part of the frame tree than where the first line occurs.
+// In particular aState may be set up for where ContentInserted or
+// ContentAppended is inserting content, which may be some
+// non-first-in-flow continuation of the block to which the first-line
+// belongs. So this function needs to be careful about how it uses
+// aState.
+void
+nsCSSFrameConstructor::WrapFramesInFirstLineFrame(
+ nsFrameConstructorState& aState,
+ nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame,
+ nsFirstLineFrame* aLineFrame,
+ nsFrameItems& aFrameItems)
+{
+ // Find the part of aFrameItems that we want to put in the first-line
+ nsFrameList::FrameLinkEnumerator link(aFrameItems);
+ while (!link.AtEnd() && link.NextFrame()->IsInlineOutside()) {
+ link.Next();
+ }
+
+ nsFrameList firstLineChildren = aFrameItems.ExtractHead(link);
+
+ if (firstLineChildren.IsEmpty()) {
+ // Nothing is supposed to go into the first-line; nothing to do
+ return;
+ }
+
+ if (!aLineFrame) {
+ // Create line frame
+ nsStyleContext* parentStyle =
+ nsFrame::CorrectStyleParentFrame(aBlockFrame,
+ nsCSSPseudoElements::firstLine)->
+ StyleContext();
+ RefPtr<nsStyleContext> firstLineStyle = GetFirstLineStyle(aBlockContent,
+ parentStyle);
+
+ aLineFrame = NS_NewFirstLineFrame(mPresShell, firstLineStyle);
+
+ // Initialize the line frame
+ InitAndRestoreFrame(aState, aBlockContent, aBlockFrame, aLineFrame);
+
+ // The lineFrame will be the block's first child; the rest of the
+ // frame list (after lastInlineFrame) will be the second and
+ // subsequent children; insert lineFrame into aFrameItems.
+ aFrameItems.InsertFrame(nullptr, nullptr, aLineFrame);
+
+ NS_ASSERTION(aLineFrame->StyleContext() == firstLineStyle,
+ "Bogus style context on line frame");
+ }
+
+ // Give the inline frames to the lineFrame <b>after</b> reparenting them
+ ReparentFrames(this, aLineFrame, firstLineChildren);
+ if (aLineFrame->PrincipalChildList().IsEmpty() &&
+ (aLineFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ aLineFrame->SetInitialChildList(kPrincipalList, firstLineChildren);
+ } else {
+ AppendFrames(aLineFrame, kPrincipalList, firstLineChildren);
+ }
+}
+
+// Special routine to handle appending a new frame to a block frame's
+// child list. Takes care of placing the new frame into the right
+// place when first-line style is present.
+void
+nsCSSFrameConstructor::AppendFirstLineFrames(
+ nsFrameConstructorState& aState,
+ nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame,
+ nsFrameItems& aFrameItems)
+{
+ // It's possible that aBlockFrame needs to have a first-line frame
+ // created because it doesn't currently have any children.
+ const nsFrameList& blockKids = aBlockFrame->PrincipalChildList();
+ if (blockKids.IsEmpty()) {
+ WrapFramesInFirstLineFrame(aState, aBlockContent,
+ aBlockFrame, nullptr, aFrameItems);
+ return;
+ }
+
+ // Examine the last block child - if it's a first-line frame then
+ // appended frames need special treatment.
+ nsIFrame* lastBlockKid = blockKids.LastChild();
+ if (lastBlockKid->GetType() != nsGkAtoms::lineFrame) {
+ // No first-line frame at the end of the list, therefore there is
+ // an intervening block between any first-line frame the frames
+ // we are appending. Therefore, we don't need any special
+ // treatment of the appended frames.
+ return;
+ }
+
+ nsFirstLineFrame* lineFrame = static_cast<nsFirstLineFrame*>(lastBlockKid);
+ WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame,
+ lineFrame, aFrameItems);
+}
+
+// Special routine to handle inserting a new frame into a block
+// frame's child list. Takes care of placing the new frame into the
+// right place when first-line style is present.
+nsresult
+nsCSSFrameConstructor::InsertFirstLineFrames(
+ nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsIFrame* aBlockFrame,
+ nsContainerFrame** aParentFrame,
+ nsIFrame* aPrevSibling,
+ nsFrameItems& aFrameItems)
+{
+ nsresult rv = NS_OK;
+ // XXXbz If you make this method actually do something, check to
+ // make sure that the caller is passing what you expect. In
+ // particular, which content is aContent? And audit the rest of
+ // this code too; it makes bogus assumptions and may not build.
+#if 0
+ nsIFrame* parentFrame = *aParentFrame;
+ nsIFrame* newFrame = aFrameItems.childList;
+ bool isInline = IsInlineOutside(newFrame);
+
+ if (!aPrevSibling) {
+ // Insertion will become the first frame. Two cases: we either
+ // already have a first-line frame or we don't.
+ nsIFrame* firstBlockKid = aBlockFrame->PrincipalChildList().FirstChild();
+ if (firstBlockKid->GetType() == nsGkAtoms::lineFrame) {
+ // We already have a first-line frame
+ nsIFrame* lineFrame = firstBlockKid;
+
+ if (isInline) {
+ // Easy case: the new inline frame will go into the lineFrame.
+ ReparentFrame(this, lineFrame, newFrame);
+ InsertFrames(lineFrame, kPrincipalList, nullptr, newFrame);
+
+ // Since the frame is going into the lineFrame, don't let it
+ // go into the block too.
+ aFrameItems.childList = nullptr;
+ aFrameItems.lastChild = nullptr;
+ }
+ else {
+ // Harder case: We are about to insert a block level element
+ // before the first-line frame.
+ // XXX need a method to steal away frames from the line-frame
+ }
+ }
+ else {
+ // We do not have a first-line frame
+ if (isInline) {
+ // We now need a first-line frame to contain the inline frame.
+ nsIFrame* lineFrame = NS_NewFirstLineFrame(firstLineStyle);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Lookup first-line style context
+ nsStyleContext* parentStyle =
+ nsFrame::CorrectStyleParentFrame(aBlockFrame,
+ nsCSSPseudoElements::firstLine)->
+ StyleContext();
+ RefPtr<nsStyleContext> firstLineStyle =
+ GetFirstLineStyle(aContent, parentStyle);
+
+ // Initialize the line frame
+ InitAndRestoreFrame(aState, aContent, aBlockFrame, lineFrame);
+
+ // Make sure the caller inserts the lineFrame into the
+ // blocks list of children.
+ aFrameItems.childList = lineFrame;
+ aFrameItems.lastChild = lineFrame;
+
+ // Give the inline frames to the lineFrame <b>after</b>
+ // reparenting them
+ NS_ASSERTION(lineFrame->StyleContext() == firstLineStyle,
+ "Bogus style context on line frame");
+ ReparentFrame(aPresContext, lineFrame, newFrame);
+ lineFrame->SetInitialChildList(kPrincipalList, newFrame);
+ }
+ }
+ else {
+ // Easy case: the regular insertion logic can insert the new
+ // frame because it's a block frame.
+ }
+ }
+ }
+ else {
+ // Insertion will not be the first frame.
+ nsIFrame* prevSiblingParent = aPrevSibling->GetParent();
+ if (prevSiblingParent == aBlockFrame) {
+ // Easy case: The prev-siblings parent is the block
+ // frame. Therefore the prev-sibling is not currently in a
+ // line-frame. Therefore the new frame which is going after it,
+ // regardless of type, is not going into a line-frame.
+ }
+ else {
+ // If the prevSiblingParent is not the block-frame then it must
+ // be a line-frame (if it were a letter-frame, that logic would
+ // already have adjusted the prev-sibling to be the
+ // letter-frame).
+ if (isInline) {
+ // Easy case: the insertion can go where the caller thinks it
+ // should go (which is into prevSiblingParent).
+ }
+ else {
+ // Block elements don't end up in line-frames, therefore
+ // change the insertion point to aBlockFrame. However, there
+ // might be more inline elements following aPrevSibling that
+ // need to be pulled out of the line-frame and become children
+ // of the block.
+ nsIFrame* nextSibling = aPrevSibling->GetNextSibling();
+ nsIFrame* nextLineFrame = prevSiblingParent->GetNextInFlow();
+ if (nextSibling || nextLineFrame) {
+ // Oy. We have work to do. Create a list of the new frames
+ // that are going into the block by stripping them away from
+ // the line-frame(s).
+ if (nextSibling) {
+ nsLineFrame* lineFrame = (nsLineFrame*) prevSiblingParent;
+ nsFrameList tail = lineFrame->StealFramesAfter(aPrevSibling);
+ // XXX do something with 'tail'
+ }
+
+ nsLineFrame* nextLineFrame = (nsLineFrame*) lineFrame;
+ for (;;) {
+ nextLineFrame = nextLineFrame->GetNextInFlow();
+ if (!nextLineFrame) {
+ break;
+ }
+ nsIFrame* kids = nextLineFrame->PrincipalChildList().FirstChild();
+ }
+ }
+ else {
+ // We got lucky: aPrevSibling was the last inline frame in
+ // the line-frame.
+ ReparentFrame(this, aBlockFrame, newFrame);
+ InsertFrames(aBlockFrame, kPrincipalList,
+ prevSiblingParent, newFrame);
+ aFrameItems.childList = nullptr;
+ aFrameItems.lastChild = nullptr;
+ }
+ }
+ }
+ }
+
+#endif
+ return rv;
+}
+
+//----------------------------------------------------------------------
+
+// First-letter support
+
+// Determine how many characters in the text fragment apply to the
+// first letter
+static int32_t
+FirstLetterCount(const nsTextFragment* aFragment)
+{
+ int32_t count = 0;
+ int32_t firstLetterLength = 0;
+
+ int32_t i, n = aFragment->GetLength();
+ for (i = 0; i < n; i++) {
+ char16_t ch = aFragment->CharAt(i);
+ // FIXME: take content language into account when deciding whitespace.
+ if (dom::IsSpaceCharacter(ch)) {
+ if (firstLetterLength) {
+ break;
+ }
+ count++;
+ continue;
+ }
+ // XXX I18n
+ if ((ch == '\'') || (ch == '\"')) {
+ if (firstLetterLength) {
+ break;
+ }
+ // keep looping
+ firstLetterLength = 1;
+ }
+ else {
+ count++;
+ break;
+ }
+ }
+
+ return count;
+}
+
+static bool
+NeedFirstLetterContinuation(nsIContent* aContent)
+{
+ NS_PRECONDITION(aContent, "null ptr");
+
+ bool result = false;
+ if (aContent) {
+ const nsTextFragment* frag = aContent->GetText();
+ if (frag) {
+ int32_t flc = FirstLetterCount(frag);
+ int32_t tl = frag->GetLength();
+ if (flc < tl) {
+ result = true;
+ }
+ }
+ }
+ return result;
+}
+
+static bool IsFirstLetterContent(nsIContent* aContent)
+{
+ return aContent->TextLength() &&
+ !aContent->TextIsOnlyWhitespace();
+}
+
+/**
+ * Create a letter frame, only make it a floating frame.
+ */
+void
+nsCSSFrameConstructor::CreateFloatingLetterFrame(
+ nsFrameConstructorState& aState,
+ nsIContent* aTextContent,
+ nsIFrame* aTextFrame,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ nsFrameItems& aResult)
+{
+ nsFirstLetterFrame* letterFrame =
+ NS_NewFirstLetterFrame(mPresShell, aStyleContext);
+ // We don't want to use a text content for a non-text frame (because we want
+ // its primary frame to be a text frame). So use its parent for the
+ // first-letter.
+ nsIContent* letterContent = aTextContent->GetParent();
+ nsContainerFrame* containingBlock = aState.GetGeometricParent(
+ aStyleContext->StyleDisplay(), aParentFrame);
+ InitAndRestoreFrame(aState, letterContent, containingBlock, letterFrame);
+
+ // Init the text frame to refer to the letter frame. Make sure we
+ // get a proper style context for it (the one passed in is for the
+ // letter frame and will have the float property set on it; the text
+ // frame shouldn't have that set).
+ StyleSetHandle styleSet = mPresShell->StyleSet();
+ RefPtr<nsStyleContext> textSC = styleSet->
+ ResolveStyleForText(aTextContent, aStyleContext);
+ aTextFrame->SetStyleContextWithoutNotification(textSC);
+ InitAndRestoreFrame(aState, aTextContent, letterFrame, aTextFrame);
+
+ // And then give the text frame to the letter frame
+ SetInitialSingleChild(letterFrame, aTextFrame);
+
+ // See if we will need to continue the text frame (does it contain
+ // more than just the first-letter text or not?) If it does, then we
+ // create (in advance) a continuation frame for it.
+ nsIFrame* nextTextFrame = nullptr;
+ if (NeedFirstLetterContinuation(aTextContent)) {
+ // Create continuation
+ nextTextFrame =
+ CreateContinuingFrame(aState.mPresContext, aTextFrame, aParentFrame);
+ // Repair the continuations style context
+ nsStyleContext* parentStyleContext = aStyleContext->GetParent();
+ if (parentStyleContext) {
+ RefPtr<nsStyleContext> newSC = styleSet->
+ ResolveStyleForText(aTextContent, parentStyleContext);
+ nextTextFrame->SetStyleContext(newSC);
+ }
+ }
+
+ NS_ASSERTION(aResult.IsEmpty(), "aResult should be an empty nsFrameItems!");
+ // Put the new float before any of the floats in the block we're doing
+ // first-letter for, that is, before any floats whose parent is
+ // containingBlock.
+ nsFrameList::FrameLinkEnumerator link(aState.mFloatedItems);
+ while (!link.AtEnd() && link.NextFrame()->GetParent() != containingBlock) {
+ link.Next();
+ }
+
+ aState.AddChild(letterFrame, aResult, letterContent, aStyleContext,
+ aParentFrame, false, true, false, true,
+ link.PrevFrame());
+
+ if (nextTextFrame) {
+ aResult.AddChild(nextTextFrame);
+ }
+}
+
+/**
+ * Create a new letter frame for aTextFrame. The letter frame will be
+ * a child of aParentFrame.
+ */
+void
+nsCSSFrameConstructor::CreateLetterFrame(nsContainerFrame* aBlockFrame,
+ nsContainerFrame* aBlockContinuation,
+ nsIContent* aTextContent,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aResult)
+{
+ NS_PRECONDITION(aTextContent->IsNodeOfType(nsINode::eTEXT),
+ "aTextContent isn't text");
+ NS_ASSERTION(nsLayoutUtils::GetAsBlock(aBlockFrame),
+ "Not a block frame?");
+
+ // Get style context for the first-letter-frame
+ nsStyleContext* parentStyleContext =
+ nsFrame::CorrectStyleParentFrame(aParentFrame,
+ nsCSSPseudoElements::firstLetter)->
+ StyleContext();
+
+ // Use content from containing block so that we can actually
+ // find a matching style rule.
+ nsIContent* blockContent = aBlockFrame->GetContent();
+
+ // Create first-letter style rule
+ RefPtr<nsStyleContext> sc = GetFirstLetterStyle(blockContent,
+ parentStyleContext);
+ if (sc) {
+ RefPtr<nsStyleContext> textSC = mPresShell->StyleSet()->
+ ResolveStyleForText(aTextContent, sc);
+
+ // Create a new text frame (the original one will be discarded)
+ // pass a temporary stylecontext, the correct one will be set
+ // later. Start off by unsetting the primary frame for
+ // aTextContent, so it's no longer pointing to the to-be-destroyed
+ // frame.
+ // XXXbz it would be really nice to destroy the old frame _first_,
+ // then create the new one, so we could avoid this hack.
+ aTextContent->SetPrimaryFrame(nullptr);
+ nsIFrame* textFrame = NS_NewTextFrame(mPresShell, textSC);
+
+ NS_ASSERTION(aBlockContinuation == GetFloatContainingBlock(aParentFrame),
+ "Containing block is confused");
+ nsFrameConstructorState state(mPresShell,
+ GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
+ aBlockContinuation);
+
+ // Create the right type of first-letter frame
+ const nsStyleDisplay* display = sc->StyleDisplay();
+ if (display->IsFloatingStyle() && !aParentFrame->IsSVGText()) {
+ // Make a floating first-letter frame
+ CreateFloatingLetterFrame(state, aTextContent, textFrame,
+ aParentFrame, sc, aResult);
+ }
+ else {
+ // Make an inflow first-letter frame
+ nsFirstLetterFrame* letterFrame = NS_NewFirstLetterFrame(mPresShell, sc);
+
+ // Initialize the first-letter-frame. We don't want to use a text
+ // content for a non-text frame (because we want its primary frame to
+ // be a text frame). So use its parent for the first-letter.
+ nsIContent* letterContent = aTextContent->GetParent();
+ letterFrame->Init(letterContent, aParentFrame, nullptr);
+
+ InitAndRestoreFrame(state, aTextContent, letterFrame, textFrame);
+
+ SetInitialSingleChild(letterFrame, textFrame);
+ aResult.Clear();
+ aResult.AddChild(letterFrame);
+ NS_ASSERTION(!aBlockFrame->GetPrevContinuation(),
+ "should have the first continuation here");
+ aBlockFrame->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD);
+ }
+ aTextContent->SetPrimaryFrame(textFrame);
+ }
+}
+
+void
+nsCSSFrameConstructor::WrapFramesInFirstLetterFrame(
+ nsContainerFrame* aBlockFrame,
+ nsFrameItems& aBlockFrames)
+{
+ aBlockFrame->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
+
+ nsContainerFrame* parentFrame = nullptr;
+ nsIFrame* textFrame = nullptr;
+ nsIFrame* prevFrame = nullptr;
+ nsFrameItems letterFrames;
+ bool stopLooking = false;
+ WrapFramesInFirstLetterFrame(aBlockFrame, aBlockFrame, aBlockFrame,
+ aBlockFrames.FirstChild(),
+ &parentFrame, &textFrame, &prevFrame,
+ letterFrames, &stopLooking);
+ if (parentFrame) {
+ if (parentFrame == aBlockFrame) {
+ // Take textFrame out of the block's frame list and substitute the
+ // letter frame(s) instead.
+ aBlockFrames.DestroyFrame(textFrame);
+ aBlockFrames.InsertFrames(nullptr, prevFrame, letterFrames);
+ }
+ else {
+ // Take the old textFrame out of the inline parent's child list
+ RemoveFrame(kPrincipalList, textFrame);
+
+ // Insert in the letter frame(s)
+ parentFrame->InsertFrames(kPrincipalList, prevFrame, letterFrames);
+ }
+ }
+}
+
+void
+nsCSSFrameConstructor::WrapFramesInFirstLetterFrame(
+ nsContainerFrame* aBlockFrame,
+ nsContainerFrame* aBlockContinuation,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aParentFrameList,
+ nsContainerFrame** aModifiedParent,
+ nsIFrame** aTextFrame,
+ nsIFrame** aPrevFrame,
+ nsFrameItems& aLetterFrames,
+ bool* aStopLooking)
+{
+ nsIFrame* prevFrame = nullptr;
+ nsIFrame* frame = aParentFrameList;
+
+ while (frame) {
+ nsIFrame* nextFrame = frame->GetNextSibling();
+
+ nsIAtom* frameType = frame->GetType();
+ if (nsGkAtoms::textFrame == frameType) {
+ // Wrap up first-letter content in a letter frame
+ nsIContent* textContent = frame->GetContent();
+ if (IsFirstLetterContent(textContent)) {
+ // Create letter frame to wrap up the text
+ CreateLetterFrame(aBlockFrame, aBlockContinuation, textContent,
+ aParentFrame, aLetterFrames);
+
+ // Provide adjustment information for parent
+ *aModifiedParent = aParentFrame;
+ *aTextFrame = frame;
+ *aPrevFrame = prevFrame;
+ *aStopLooking = true;
+ return;
+ }
+ }
+ else if (IsInlineFrame(frame) && frameType != nsGkAtoms::brFrame) {
+ nsIFrame* kids = frame->PrincipalChildList().FirstChild();
+ WrapFramesInFirstLetterFrame(aBlockFrame, aBlockContinuation,
+ static_cast<nsContainerFrame*>(frame),
+ kids, aModifiedParent, aTextFrame,
+ aPrevFrame, aLetterFrames, aStopLooking);
+ if (*aStopLooking) {
+ return;
+ }
+ }
+ else {
+ // This will stop us looking to create more letter frames. For
+ // example, maybe the frame-type is "letterFrame" or
+ // "placeholderFrame". This keeps us from creating extra letter
+ // frames, and also prevents us from creating letter frames when
+ // the first real content child of a block is not text (e.g. an
+ // image, hr, etc.)
+ *aStopLooking = true;
+ break;
+ }
+
+ prevFrame = frame;
+ frame = nextFrame;
+ }
+}
+
+static nsIFrame*
+FindFirstLetterFrame(nsIFrame* aFrame, nsIFrame::ChildListID aListID)
+{
+ nsFrameList list = aFrame->GetChildList(aListID);
+ for (nsFrameList::Enumerator e(list); !e.AtEnd(); e.Next()) {
+ if (nsGkAtoms::letterFrame == e.get()->GetType()) {
+ return e.get();
+ }
+ }
+ return nullptr;
+}
+
+nsresult
+nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames(
+ nsIPresShell* aPresShell,
+ nsIFrame* aBlockFrame)
+{
+ // Look for the first letter frame on the kFloatList, then kPushedFloatsList.
+ nsIFrame* floatFrame =
+ ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kFloatList);
+ if (!floatFrame) {
+ floatFrame =
+ ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kPushedFloatsList);
+ if (!floatFrame) {
+ return NS_OK;
+ }
+ }
+
+ // Take the text frame away from the letter frame (so it isn't
+ // destroyed when we destroy the letter frame).
+ nsIFrame* textFrame = floatFrame->PrincipalChildList().FirstChild();
+ if (!textFrame) {
+ return NS_OK;
+ }
+
+ // Discover the placeholder frame for the letter frame
+ nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrameFor(floatFrame);
+ if (!placeholderFrame) {
+ // Somethings really wrong
+ return NS_OK;
+ }
+ nsContainerFrame* parentFrame = placeholderFrame->GetParent();
+ if (!parentFrame) {
+ // Somethings really wrong
+ return NS_OK;
+ }
+
+ // Create a new text frame with the right style context that maps
+ // all of the content that was previously part of the letter frame
+ // (and probably continued elsewhere).
+ nsStyleContext* parentSC = parentFrame->StyleContext();
+ nsIContent* textContent = textFrame->GetContent();
+ if (!textContent) {
+ return NS_OK;
+ }
+ RefPtr<nsStyleContext> newSC = aPresShell->StyleSet()->
+ ResolveStyleForText(textContent, parentSC);
+ nsIFrame* newTextFrame = NS_NewTextFrame(aPresShell, newSC);
+ newTextFrame->Init(textContent, parentFrame, nullptr);
+
+ // Destroy the old text frame's continuations (the old text frame
+ // will be destroyed when its letter frame is destroyed).
+ nsIFrame* frameToDelete = textFrame->LastContinuation();
+ while (frameToDelete != textFrame) {
+ nsIFrame* nextFrameToDelete = frameToDelete->GetPrevContinuation();
+ RemoveFrame(kPrincipalList, frameToDelete);
+ frameToDelete = nextFrameToDelete;
+ }
+
+ nsIFrame* prevSibling = placeholderFrame->GetPrevSibling();
+
+ // Now that everything is set...
+#ifdef NOISY_FIRST_LETTER
+ printf("RemoveFloatingFirstLetterFrames: textContent=%p oldTextFrame=%p newTextFrame=%p\n",
+ textContent.get(), textFrame, newTextFrame);
+#endif
+
+ // Remove placeholder frame and the float
+ RemoveFrame(kPrincipalList, placeholderFrame);
+
+ // Now that the old frames are gone, we can start pointing to our
+ // new primary frame.
+ textContent->SetPrimaryFrame(newTextFrame);
+
+ // Wallpaper bug 822910.
+ bool offsetsNeedFixing =
+ prevSibling && prevSibling->GetType() == nsGkAtoms::textFrame;
+ if (offsetsNeedFixing) {
+ prevSibling->AddStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ // Insert text frame in its place
+ nsFrameList textList(newTextFrame, newTextFrame);
+ InsertFrames(parentFrame, kPrincipalList, prevSibling, textList);
+
+ if (offsetsNeedFixing) {
+ prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFrameConstructor::RemoveFirstLetterFrames(nsIPresShell* aPresShell,
+ nsContainerFrame* aFrame,
+ nsContainerFrame* aBlockFrame,
+ bool* aStopLooking)
+{
+ nsIFrame* prevSibling = nullptr;
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+
+ while (kid) {
+ if (nsGkAtoms::letterFrame == kid->GetType()) {
+ // Bingo. Found it. First steal away the text frame.
+ nsIFrame* textFrame = kid->PrincipalChildList().FirstChild();
+ if (!textFrame) {
+ break;
+ }
+
+ // Create a new textframe
+ nsStyleContext* parentSC = aFrame->StyleContext();
+ if (!parentSC) {
+ break;
+ }
+ nsIContent* textContent = textFrame->GetContent();
+ if (!textContent) {
+ break;
+ }
+ RefPtr<nsStyleContext> newSC = aPresShell->StyleSet()->
+ ResolveStyleForText(textContent, parentSC);
+ textFrame = NS_NewTextFrame(aPresShell, newSC);
+ textFrame->Init(textContent, aFrame, nullptr);
+
+ // Next rip out the kid and replace it with the text frame
+ RemoveFrame(kPrincipalList, kid);
+
+ // Now that the old frames are gone, we can start pointing to our
+ // new primary frame.
+ textContent->SetPrimaryFrame(textFrame);
+
+ // Wallpaper bug 822910.
+ bool offsetsNeedFixing =
+ prevSibling && prevSibling->GetType() == nsGkAtoms::textFrame;
+ if (offsetsNeedFixing) {
+ prevSibling->AddStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ // Insert text frame in its place
+ nsFrameList textList(textFrame, textFrame);
+ InsertFrames(aFrame, kPrincipalList, prevSibling, textList);
+
+ if (offsetsNeedFixing) {
+ prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ *aStopLooking = true;
+ NS_ASSERTION(!aBlockFrame->GetPrevContinuation(),
+ "should have the first continuation here");
+ aBlockFrame->RemoveStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD);
+ break;
+ }
+ else if (IsInlineFrame(kid)) {
+ nsContainerFrame* kidAsContainerFrame = do_QueryFrame(kid);
+ if (kidAsContainerFrame) {
+ // Look inside child inline frame for the letter frame.
+ RemoveFirstLetterFrames(aPresShell, kidAsContainerFrame,
+ aBlockFrame, aStopLooking);
+ if (*aStopLooking) {
+ break;
+ }
+ }
+ }
+ prevSibling = kid;
+ kid = kid->GetNextSibling();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFrameConstructor::RemoveLetterFrames(nsIPresShell* aPresShell,
+ nsContainerFrame* aBlockFrame)
+{
+ aBlockFrame =
+ static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
+ nsContainerFrame* continuation = aBlockFrame;
+
+ bool stopLooking = false;
+ nsresult rv;
+ do {
+ rv = RemoveFloatingFirstLetterFrames(aPresShell, continuation);
+ if (NS_SUCCEEDED(rv)) {
+ rv = RemoveFirstLetterFrames(aPresShell,
+ continuation, aBlockFrame, &stopLooking);
+ }
+ if (stopLooking) {
+ break;
+ }
+ continuation =
+ static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
+ } while (continuation);
+ return rv;
+}
+
+// Fixup the letter frame situation for the given block
+void
+nsCSSFrameConstructor::RecoverLetterFrames(nsContainerFrame* aBlockFrame)
+{
+ aBlockFrame =
+ static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
+ nsContainerFrame* continuation = aBlockFrame;
+
+ nsContainerFrame* parentFrame = nullptr;
+ nsIFrame* textFrame = nullptr;
+ nsIFrame* prevFrame = nullptr;
+ nsFrameItems letterFrames;
+ bool stopLooking = false;
+ do {
+ // XXX shouldn't this bit be set already (bug 408493), assert instead?
+ continuation->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
+ WrapFramesInFirstLetterFrame(aBlockFrame, continuation, continuation,
+ continuation->PrincipalChildList().FirstChild(),
+ &parentFrame, &textFrame, &prevFrame,
+ letterFrames, &stopLooking);
+ if (stopLooking) {
+ break;
+ }
+ continuation =
+ static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
+ } while (continuation);
+
+ if (parentFrame) {
+ // Take the old textFrame out of the parents child list
+ RemoveFrame(kPrincipalList, textFrame);
+
+ // Insert in the letter frame(s)
+ parentFrame->InsertFrames(kPrincipalList, prevFrame, letterFrames);
+ }
+}
+
+//----------------------------------------------------------------------
+
+// listbox Widget Routines
+
+nsresult
+nsCSSFrameConstructor::CreateListBoxContent(nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevFrame,
+ nsIContent* aChild,
+ nsIFrame** aNewFrame,
+ bool aIsAppend)
+{
+#ifdef MOZ_XUL
+ nsresult rv = NS_OK;
+
+ // Construct a new frame
+ if (nullptr != aParentFrame) {
+ nsFrameItems frameItems;
+ nsFrameConstructorState state(mPresShell, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
+ GetFloatContainingBlock(aParentFrame),
+ do_AddRef(mTempFrameTreeState.get()));
+
+ // If we ever initialize the ancestor filter on |state|, make sure
+ // to push the right parent!
+
+ RefPtr<nsStyleContext> styleContext;
+ styleContext = ResolveStyleContext(aParentFrame, aChild, &state);
+
+ // Pre-check for display "none" - only if we find that, do we create
+ // any frame at all
+ const nsStyleDisplay* display = styleContext->StyleDisplay();
+
+ if (StyleDisplay::None == display->mDisplay) {
+ *aNewFrame = nullptr;
+ return NS_OK;
+ }
+
+ BeginUpdate();
+
+ FrameConstructionItemList items;
+ AddFrameConstructionItemsInternal(state, aChild, aParentFrame,
+ aChild->NodeInfo()->NameAtom(),
+ aChild->GetNameSpaceID(),
+ true, styleContext,
+ ITEM_ALLOW_XBL_BASE, nullptr, items);
+ ConstructFramesFromItemList(state, items, aParentFrame, frameItems);
+
+ nsIFrame* newFrame = frameItems.FirstChild();
+ *aNewFrame = newFrame;
+
+ if (newFrame) {
+ // Notify the parent frame
+ if (aIsAppend)
+ rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems);
+ else
+ rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems);
+ }
+
+ EndUpdate();
+
+#ifdef ACCESSIBILITY
+ if (newFrame) {
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->ContentRangeInserted(mPresShell, aChild->GetParent(),
+ aChild, aChild->GetNextSibling());
+ }
+ }
+#endif
+ }
+
+ return rv;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+//----------------------------------------
+
+void
+nsCSSFrameConstructor::ConstructBlock(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame* aContentParentFrame,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame** aNewFrame,
+ nsFrameItems& aFrameItems,
+ nsIFrame* aPositionedFrameForAbsPosContainer,
+ PendingBinding* aPendingBinding)
+{
+ // Create column wrapper if necessary
+ nsContainerFrame* blockFrame = *aNewFrame;
+ NS_ASSERTION((blockFrame->GetType() == nsGkAtoms::blockFrame ||
+ blockFrame->GetType() == nsGkAtoms::detailsFrame),
+ "not a block frame nor a details frame?");
+ nsContainerFrame* parent = aParentFrame;
+ RefPtr<nsStyleContext> blockStyle = aStyleContext;
+ const nsStyleColumn* columns = aStyleContext->StyleColumn();
+
+ if (columns->mColumnCount != NS_STYLE_COLUMN_COUNT_AUTO
+ || columns->mColumnWidth.GetUnit() != eStyleUnit_Auto) {
+ nsContainerFrame* columnSetFrame =
+ NS_NewColumnSetFrame(mPresShell, aStyleContext, nsFrameState(0));
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, columnSetFrame);
+ blockStyle = mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::columnContent, aStyleContext);
+ parent = columnSetFrame;
+ *aNewFrame = columnSetFrame;
+ if (aPositionedFrameForAbsPosContainer == blockFrame) {
+ aPositionedFrameForAbsPosContainer = columnSetFrame;
+ }
+
+ SetInitialSingleChild(columnSetFrame, blockFrame);
+ }
+
+ blockFrame->SetStyleContextWithoutNotification(blockStyle);
+ InitAndRestoreFrame(aState, aContent, parent, blockFrame);
+
+ aState.AddChild(*aNewFrame, aFrameItems, aContent, aStyleContext,
+ aContentParentFrame ? aContentParentFrame :
+ aParentFrame);
+ if (!mRootElementFrame) {
+ // The frame we're constructing will be the root element frame.
+ // Set mRootElementFrame before processing children.
+ mRootElementFrame = *aNewFrame;
+ }
+
+ // We should make the outer frame be the absolute containing block,
+ // if one is required. We have to do this because absolute
+ // positioning must be computed with respect to the CSS dimensions
+ // of the element, which are the dimensions of the outer block. But
+ // we can't really do that because only blocks can have absolute
+ // children. So use the block and try to compensate with hacks
+ // in nsBlockFrame::CalculateContainingBlockSizeForAbsolutes.
+ nsFrameConstructorSaveState absoluteSaveState;
+ (*aNewFrame)->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (aPositionedFrameForAbsPosContainer) {
+ // NS_ASSERTION(aRelPos, "should have made area frame for this");
+ aState.PushAbsoluteContainingBlock(*aNewFrame, aPositionedFrameForAbsPosContainer, absoluteSaveState);
+ }
+
+ // Process the child content
+ nsFrameItems childItems;
+ ProcessChildren(aState, aContent, aStyleContext, blockFrame, true,
+ childItems, true, aPendingBinding);
+
+ // Set the frame's initial child list
+ blockFrame->SetInitialChildList(kPrincipalList, childItems);
+}
+
+nsIFrame*
+nsCSSFrameConstructor::ConstructInline(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems)
+{
+ // If an inline frame has non-inline kids, then we chop up the child list
+ // into runs of blocks and runs of inlines, create anonymous block frames to
+ // contain the runs of blocks, inline frames with our style context for the
+ // runs of inlines, and put all these frames, in order, into aFrameItems. We
+ // return the the first one. The whole setup is called an {ib}
+ // split; in what follows "frames in the split" refers to the anonymous blocks
+ // and inlines that contain our children.
+ //
+ // {ib} splits maintain the following invariants:
+ // 1) All frames in the split have the NS_FRAME_PART_OF_IBSPLIT bit
+ // set.
+ // 2) Each frame in the split has the nsIFrame::IBSplitSibling
+ // property pointing to the next frame in the split, except for the last
+ // one, which does not have it set.
+ // 3) Each frame in the split has the nsIFrame::IBSplitPrevSibling
+ // property pointing to the previous frame in the split, except for the
+ // first one, which does not have it set.
+ // 4) The first and last frame in the split are always inlines.
+ //
+ // An invariant that is NOT maintained is that the wrappers are actually
+ // linked via GetNextSibling linkage. A simple example is an inline
+ // containing an inline that contains a block. The three parts of the inner
+ // inline end up with three different parents.
+ //
+ // For example, this HTML:
+ // <span>
+ // <div>a</div>
+ // <span>
+ // b
+ // <div>c</div>
+ // </span>
+ // d
+ // <div>e</div>
+ // f
+ // </span>
+ // Gives the following frame tree:
+ //
+ // Inline (outer span)
+ // Block (anonymous, outer span)
+ // Block (div)
+ // Text("a")
+ // Inline (outer span)
+ // Inline (inner span)
+ // Text("b")
+ // Block (anonymous, outer span)
+ // Block (anonymous, inner span)
+ // Block (div)
+ // Text("c")
+ // Inline (outer span)
+ // Inline (inner span)
+ // Text("d")
+ // Block (anonymous, outer span)
+ // Block (div)
+ // Text("e")
+ // Inline (outer span)
+ // Text("f")
+
+ nsIContent* const content = aItem.mContent;
+ nsStyleContext* const styleContext = aItem.mStyleContext;
+
+ bool positioned =
+ StyleDisplay::Inline == aDisplay->mDisplay &&
+ aDisplay->IsRelativelyPositionedStyle() &&
+ !aParentFrame->IsSVGText();
+
+ nsInlineFrame* newFrame = NS_NewInlineFrame(mPresShell, styleContext);
+
+ // Initialize the frame
+ InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+
+ // Inline frames can always have generated content
+ newFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT);
+
+ nsFrameConstructorSaveState absoluteSaveState; // definition cannot be inside next block
+ // because the object's destructor is significant
+ // this is part of the fix for bug 42372
+
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (positioned) {
+ // Relatively positioned frames becomes a container for child
+ // frames that are positioned
+ aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
+ }
+
+ // Process the child content
+ nsFrameItems childItems;
+ ConstructFramesFromItemList(aState, aItem.mChildItems, newFrame, childItems);
+
+ nsFrameList::FrameLinkEnumerator firstBlockEnumerator(childItems);
+ if (!aItem.mIsAllInline) {
+ FindFirstBlock(firstBlockEnumerator);
+ }
+
+ if (aItem.mIsAllInline || firstBlockEnumerator.AtEnd()) {
+ // This part is easy. We either already know we have no non-inline kids,
+ // or haven't found any when constructing actual frames (the latter can
+ // happen only if out-of-flows that we thought had no containing block
+ // acquired one when ancestor inline frames and {ib} splits got
+ // constructed). Just put all the kids into the single inline frame and
+ // bail.
+ newFrame->SetInitialChildList(kPrincipalList, childItems);
+ aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
+ return newFrame;
+ }
+
+ // This inline frame contains several types of children. Therefore this frame
+ // has to be chopped into several pieces, as described above.
+
+ // Grab the first inline's kids
+ nsFrameList firstInlineKids = childItems.ExtractHead(firstBlockEnumerator);
+ newFrame->SetInitialChildList(kPrincipalList, firstInlineKids);
+
+ aFrameItems.AddChild(newFrame);
+
+ CreateIBSiblings(aState, newFrame, positioned, childItems, aFrameItems);
+
+ return newFrame;
+}
+
+void
+nsCSSFrameConstructor::CreateIBSiblings(nsFrameConstructorState& aState,
+ nsContainerFrame* aInitialInline,
+ bool aIsPositioned,
+ nsFrameItems& aChildItems,
+ nsFrameItems& aSiblings)
+{
+ nsIContent* content = aInitialInline->GetContent();
+ nsStyleContext* styleContext = aInitialInline->StyleContext();
+ nsContainerFrame* parentFrame = aInitialInline->GetParent();
+
+ // Resolve the right style context for our anonymous blocks.
+ // The distinction in styles is needed because of CSS 2.1, section
+ // 9.2.1.1, which says:
+ // When such an inline box is affected by relative positioning, any
+ // resulting translation also affects the block-level box contained
+ // in the inline box.
+ RefPtr<nsStyleContext> blockSC =
+ mPresShell->StyleSet()->
+ ResolveAnonymousBoxStyle(aIsPositioned ?
+ nsCSSAnonBoxes::mozAnonymousPositionedBlock :
+ nsCSSAnonBoxes::mozAnonymousBlock,
+ styleContext);
+
+ nsContainerFrame* lastNewInline =
+ static_cast<nsContainerFrame*>(aInitialInline->FirstContinuation());
+ do {
+ // On entry to this loop aChildItems is not empty and the first frame in it
+ // is block-level.
+ NS_PRECONDITION(aChildItems.NotEmpty(), "Should have child items");
+ NS_PRECONDITION(!aChildItems.FirstChild()->IsInlineOutside(),
+ "Must have list starting with block");
+
+ // The initial run of blocks belongs to an anonymous block that we create
+ // right now. The anonymous block will be the parent of these block
+ // children of the inline.
+ nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, blockSC);
+ InitAndRestoreFrame(aState, content, parentFrame, blockFrame, false);
+
+ // Find the first non-block child which defines the end of our block kids
+ // and the start of our next inline's kids
+ nsFrameList::FrameLinkEnumerator firstNonBlock =
+ FindFirstNonBlock(aChildItems);
+ nsFrameList blockKids = aChildItems.ExtractHead(firstNonBlock);
+
+ MoveChildrenTo(aInitialInline, blockFrame, blockKids);
+
+ SetFrameIsIBSplit(lastNewInline, blockFrame);
+ aSiblings.AddChild(blockFrame);
+
+ // Now grab the initial inlines in aChildItems and put them into an inline
+ // frame.
+ nsInlineFrame* inlineFrame = NS_NewInlineFrame(mPresShell, styleContext);
+ InitAndRestoreFrame(aState, content, parentFrame, inlineFrame, false);
+ inlineFrame->AddStateBits(NS_FRAME_MAY_HAVE_GENERATED_CONTENT |
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (aIsPositioned) {
+ inlineFrame->MarkAsAbsoluteContainingBlock();
+ }
+
+ if (aChildItems.NotEmpty()) {
+ nsFrameList::FrameLinkEnumerator firstBlock(aChildItems);
+ FindFirstBlock(firstBlock);
+ nsFrameList inlineKids = aChildItems.ExtractHead(firstBlock);
+
+ MoveChildrenTo(aInitialInline, inlineFrame, inlineKids);
+ }
+
+ SetFrameIsIBSplit(blockFrame, inlineFrame);
+ aSiblings.AddChild(inlineFrame);
+ lastNewInline = inlineFrame;
+ } while (aChildItems.NotEmpty());
+
+ SetFrameIsIBSplit(lastNewInline, nullptr);
+}
+
+void
+nsCSSFrameConstructor::BuildInlineChildItems(nsFrameConstructorState& aState,
+ FrameConstructionItem& aParentItem,
+ bool aItemIsWithinSVGText,
+ bool aItemAllowsTextPathChild)
+{
+ // XXXbz should we preallocate aParentItem.mChildItems to some sane
+ // length? Maybe even to parentContent->GetChildCount()?
+ nsFrameConstructorState::PendingBindingAutoPusher
+ pusher(aState, aParentItem.mPendingBinding);
+
+ nsStyleContext* const parentStyleContext = aParentItem.mStyleContext;
+ nsIContent* const parentContent = aParentItem.mContent;
+
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(aState.mTreeMatchContext);
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ ancestorPusher.PushAncestorAndStyleScope(parentContent->AsElement());
+ } else {
+ ancestorPusher.PushStyleScope(parentContent->AsElement());
+ }
+
+ if (!aItemIsWithinSVGText) {
+ // Probe for generated content before
+ CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
+ CSSPseudoElementType::before,
+ aParentItem.mChildItems);
+ }
+
+ uint32_t flags = ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK;
+ if (aItemIsWithinSVGText) {
+ flags |= ITEM_IS_WITHIN_SVG_TEXT;
+ }
+ if (aItemAllowsTextPathChild && aParentItem.mIsForSVGAElement) {
+ flags |= ITEM_ALLOWS_TEXT_PATH_CHILD;
+ }
+
+ if (!aParentItem.mAnonChildren.IsEmpty()) {
+ // Use the anon-children list instead of the content tree child list so
+ // that we use any special style context that should be associated with
+ // the children, and so that we won't try to construct grandchildren frame
+ // constructor items before the frame is available for their parent.
+ AddFCItemsForAnonymousContent(aState, nullptr, aParentItem.mAnonChildren,
+ aParentItem.mChildItems, flags);
+ } else {
+ // Use the content tree child list:
+ FlattenedChildIterator iter(parentContent);
+ for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) {
+ // Get the parent of the content and check if it is a XBL children element
+ // (if the content is a children element then contentParent != parentContent because the
+ // FlattenedChildIterator will transitively iterate through <xbl:children>
+ // for default content). Push the children element as an ancestor here because
+ // it does not have a frame and would not otherwise be pushed as an ancestor.
+ nsIContent* contentParent = content->GetParent();
+ MOZ_ASSERT(contentParent, "Parent must be non-null because we are iterating children.");
+ TreeMatchContext::AutoAncestorPusher insertionPointPusher(aState.mTreeMatchContext);
+ if (contentParent != parentContent && contentParent->IsElement()) {
+ if (aState.mTreeMatchContext.mAncestorFilter.HasFilter()) {
+ insertionPointPusher.PushAncestorAndStyleScope(contentParent->AsElement());
+ } else {
+ insertionPointPusher.PushStyleScope(contentParent->AsElement());
+ }
+ }
+
+ // Manually check for comments/PIs, since we don't have a frame to pass to
+ // AddFrameConstructionItems. We know our parent is a non-replaced inline,
+ // so there is no need to do the NeedFrameFor check.
+ content->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ if (content->IsNodeOfType(nsINode::eCOMMENT) ||
+ content->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
+ continue;
+ }
+
+ // See comment explaining why we need to remove the "is possible
+ // restyle root" flags in AddFrameConstructionItems. But note
+ // that we can remove all restyle flags, just like in
+ // ProcessChildren and for the same reason.
+ content->UnsetRestyleFlagsIfGecko();
+
+ RefPtr<nsStyleContext> childContext =
+ ResolveStyleContext(parentStyleContext, content, &aState);
+
+ AddFrameConstructionItemsInternal(aState, content, nullptr,
+ content->NodeInfo()->NameAtom(),
+ content->GetNameSpaceID(),
+ iter.XBLInvolved(), childContext,
+ flags, nullptr,
+ aParentItem.mChildItems);
+ }
+ }
+
+ if (!aItemIsWithinSVGText) {
+ // Probe for generated content after
+ CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
+ CSSPseudoElementType::after,
+ aParentItem.mChildItems);
+ }
+
+ aParentItem.mIsAllInline = aParentItem.mChildItems.AreAllItemsInline();
+}
+
+// return whether it's ok to append (in the AppendFrames sense) to
+// aParentFrame if our nextSibling is aNextSibling. aParentFrame must
+// be an ib-split inline.
+static bool
+IsSafeToAppendToIBSplitInline(nsIFrame* aParentFrame, nsIFrame* aNextSibling)
+{
+ NS_PRECONDITION(IsInlineFrame(aParentFrame),
+ "Must have an inline parent here");
+ do {
+ NS_ASSERTION(IsFramePartOfIBSplit(aParentFrame),
+ "How is this not part of an ib-split?");
+ if (aNextSibling || aParentFrame->GetNextContinuation() ||
+ GetIBSplitSibling(aParentFrame)) {
+ return false;
+ }
+
+ aNextSibling = aParentFrame->GetNextSibling();
+ aParentFrame = aParentFrame->GetParent();
+ } while (IsInlineFrame(aParentFrame));
+
+ return true;
+}
+
+bool
+nsCSSFrameConstructor::WipeContainingBlock(nsFrameConstructorState& aState,
+ nsIFrame* aContainingBlock,
+ nsIFrame* aFrame,
+ FrameConstructionItemList& aItems,
+ bool aIsAppend,
+ nsIFrame* aPrevSibling)
+{
+ if (aItems.IsEmpty()) {
+ return false;
+ }
+
+ // Before we go and append the frames, we must check for several
+ // special situations.
+
+ // Situation #1 is a XUL frame that contains frames that are required
+ // to be wrapped in blocks.
+ if (aFrame->IsXULBoxFrame() &&
+ !(aFrame->GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) &&
+ aItems.AnyItemsNeedBlockParent()) {
+ RecreateFramesForContent(aFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+
+ nsIFrame* nextSibling = ::GetInsertNextSibling(aFrame, aPrevSibling);
+
+ // Situation #2 is a flex or grid container frame into which we're inserting
+ // new inline non-replaced children, adjacent to an existing anonymous
+ // flex or grid item.
+ nsIAtom* frameType = aFrame->GetType();
+ if (frameType == nsGkAtoms::flexContainerFrame ||
+ frameType == nsGkAtoms::gridContainerFrame) {
+ FCItemIterator iter(aItems);
+
+ // Check if we're adding to-be-wrapped content right *after* an existing
+ // anonymous flex or grid item (which would need to absorb this content).
+ const bool isWebkitBox = IsFlexContainerForLegacyBox(aFrame, frameType);
+ if (aPrevSibling && IsAnonymousFlexOrGridItem(aPrevSibling) &&
+ iter.item().NeedsAnonFlexOrGridItem(aState, isWebkitBox)) {
+ RecreateFramesForContent(aFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+
+ // Check if we're adding to-be-wrapped content right *before* an existing
+ // anonymous flex or grid item (which would need to absorb this content).
+ if (nextSibling && IsAnonymousFlexOrGridItem(nextSibling)) {
+ // Jump to the last entry in the list
+ iter.SetToEnd();
+ iter.Prev();
+ if (iter.item().NeedsAnonFlexOrGridItem(aState, isWebkitBox)) {
+ RecreateFramesForContent(aFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+ }
+ }
+
+ // Situation #3 is an anonymous flex or grid item that's getting new children
+ // who don't want to be wrapped.
+ if (IsAnonymousFlexOrGridItem(aFrame)) {
+ AssertAnonymousFlexOrGridItemParent(aFrame, aFrame->GetParent());
+
+ // We need to push a null float containing block to be sure that
+ // "NeedsAnonFlexOrGridItem" will know we're not honoring floats for this
+ // inserted content. (In particular, this is necessary in order for
+ // its "GetGeometricParent" call to return the correct result.)
+ // We're not honoring floats on this content because it has the
+ // _flex/grid container_ as its parent in the content tree.
+ nsFrameConstructorSaveState floatSaveState;
+ aState.PushFloatContainingBlock(nullptr, floatSaveState);
+
+ FCItemIterator iter(aItems);
+ // Skip over things that _do_ need an anonymous flex item, because
+ // they're perfectly happy to go here -- they won't cause a reframe.
+ nsIFrame* containerFrame = aFrame->GetParent();
+ const bool isWebkitBox =
+ IsFlexContainerForLegacyBox(containerFrame, containerFrame->GetType());
+ if (!iter.SkipItemsThatNeedAnonFlexOrGridItem(aState,
+ isWebkitBox)) {
+ // We hit something that _doesn't_ need an anonymous flex item!
+ // Rebuild the flex container to bust it out.
+ RecreateFramesForContent(containerFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+
+ // If we get here, then everything in |aItems| needs to be wrapped in
+ // an anonymous flex or grid item. That's where it's already going - good!
+ }
+
+ // Situation #4 is a ruby-related frame that's getting new children.
+ // The situation for ruby is complex, especially when interacting with
+ // spaces. It containes these two special cases apart from tables:
+ // 1) There are effectively three types of white spaces in ruby frames
+ // we handle differently: leading/tailing/inter-level space,
+ // inter-base/inter-annotation space, and inter-segment space.
+ // These three types of spaces can be converted to each other when
+ // their sibling changes.
+ // 2) The first effective child of a ruby frame must always be a ruby
+ // base container. It should be created or destroyed accordingly.
+ if (IsRubyPseudo(aFrame) ||
+ frameType == nsGkAtoms::rubyFrame ||
+ RubyUtils::IsRubyContainerBox(frameType)) {
+ // We want to optimize it better, and avoid reframing as much as
+ // possible. But given the cases above, and the fact that a ruby
+ // usually won't be very large, it should be fine to reframe it.
+ RecreateFramesForContent(aFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+
+ // Situation #5 is a case when table pseudo-frames don't work out right
+ ParentType parentType = GetParentType(aFrame);
+ // If all the kids want a parent of the type that aFrame is, then we're all
+ // set to go. Indeed, there won't be any table pseudo-frames created between
+ // aFrame and the kids, so those won't need to be merged with any table
+ // pseudo-frames that might already be kids of aFrame. If aFrame itself is a
+ // table pseudo-frame, then all the kids in this list would have wanted a
+ // frame of that type wrapping them anyway, so putting them inside it is ok.
+ if (!aItems.AllWantParentType(parentType)) {
+ // Don't give up yet. If parentType is not eTypeBlock and the parent is
+ // not a generated content frame, then try filtering whitespace out of the
+ // list.
+ if (parentType != eTypeBlock && !aFrame->IsGeneratedContentFrame()) {
+ // For leading whitespace followed by a kid that wants our parent type,
+ // there are four cases:
+ // 1) We have a previous sibling which is not a table pseudo. That means
+ // that previous sibling wanted a (non-block) parent of the type we're
+ // looking at. Then the whitespace comes between two table-internal
+ // elements, so should be collapsed out.
+ // 2) We have a previous sibling which is a table pseudo. It might have
+ // kids who want this whitespace, so we need to reframe.
+ // 3) We have no previous sibling and our parent frame is not a table
+ // pseudo. That means that we'll be at the beginning of our actual
+ // non-block-type parent, and the whitespace is OK to collapse out.
+ // If something is ever inserted before us, it'll find our own parent
+ // as its parent and if it's something that would care about the
+ // whitespace it'll want a block parent, so it'll trigger a reframe at
+ // that point.
+ // 4) We have no previous sibling and our parent frame is a table pseudo.
+ // Need to reframe.
+ // All that is predicated on finding the correct previous sibling. We
+ // might have to walk backwards along continuations from aFrame to do so.
+ //
+ // It's always OK to drop whitespace between any two items that want a
+ // parent of type parentType.
+ //
+ // For trailing whitespace preceded by a kid that wants our parent type,
+ // there are four cases:
+ // 1) We have a next sibling which is not a table pseudo. That means
+ // that next sibling wanted a (non-block) parent of the type we're
+ // looking at. Then the whitespace comes between two table-internal
+ // elements, so should be collapsed out.
+ // 2) We have a next sibling which is a table pseudo. It might have
+ // kids who want this whitespace, so we need to reframe.
+ // 3) We have no next sibling and our parent frame is not a table
+ // pseudo. That means that we'll be at the end of our actual
+ // non-block-type parent, and the whitespace is OK to collapse out.
+ // If something is ever inserted after us, it'll find our own parent
+ // as its parent and if it's something that would care about the
+ // whitespace it'll want a block parent, so it'll trigger a reframe at
+ // that point.
+ // 4) We have no next sibling and our parent frame is a table pseudo.
+ // Need to reframe.
+ // All that is predicated on finding the correct next sibling. We might
+ // have to walk forward along continuations from aFrame to do so. That
+ // said, in the case when nextSibling is null at this point and aIsAppend
+ // is true, we know we're in case 3. Furthermore, in that case we don't
+ // even have to worry about the table pseudo situation; we know our
+ // parent is not a table pseudo there.
+ FCItemIterator iter(aItems);
+ FCItemIterator start(iter);
+ do {
+ if (iter.SkipItemsWantingParentType(parentType)) {
+ break;
+ }
+
+ // iter points to an item that wants a different parent. If it's not
+ // whitespace, we're done; no more point scanning the list.
+ if (!iter.item().IsWhitespace(aState)) {
+ break;
+ }
+
+ if (iter == start) {
+ // Leading whitespace. How to handle this depends on our
+ // previous sibling and aFrame. See the long comment above.
+ nsIFrame* prevSibling = aPrevSibling;
+ if (!prevSibling) {
+ // Try to find one after all
+ nsIFrame* parentPrevCont = aFrame->GetPrevContinuation();
+ while (parentPrevCont) {
+ prevSibling = parentPrevCont->GetChildList(kPrincipalList).LastChild();
+ if (prevSibling) {
+ break;
+ }
+ parentPrevCont = parentPrevCont->GetPrevContinuation();
+ }
+ };
+ if (prevSibling) {
+ if (IsTablePseudo(prevSibling)) {
+ // need to reframe
+ break;
+ }
+ } else if (IsTablePseudo(aFrame)) {
+ // need to reframe
+ break;
+ }
+ }
+
+ FCItemIterator spaceEndIter(iter);
+ // Advance spaceEndIter past any whitespace
+ bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
+
+ bool okToDrop;
+ if (trailingSpaces) {
+ // Trailing whitespace. How to handle this depeds on aIsAppend, our
+ // next sibling and aFrame. See the long comment above.
+ okToDrop = aIsAppend && !nextSibling;
+ if (!okToDrop) {
+ if (!nextSibling) {
+ // Try to find one after all
+ nsIFrame* parentNextCont = aFrame->GetNextContinuation();
+ while (parentNextCont) {
+ nextSibling = parentNextCont->PrincipalChildList().FirstChild();
+ if (nextSibling) {
+ break;
+ }
+ parentNextCont = parentNextCont->GetNextContinuation();
+ }
+ }
+
+ okToDrop = (nextSibling && !IsTablePseudo(nextSibling)) ||
+ (!nextSibling && !IsTablePseudo(aFrame));
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(!IsTablePseudo(aFrame), "How did that happen?");
+ }
+#endif
+ } else {
+ okToDrop = (spaceEndIter.item().DesiredParentType() == parentType);
+ }
+
+ if (okToDrop) {
+ iter.DeleteItemsTo(spaceEndIter);
+ } else {
+ // We're done: we don't want to drop the whitespace, and it has the
+ // wrong parent type.
+ break;
+ }
+
+ // Now loop, since |iter| points to item right after the whitespace we
+ // removed.
+ } while (!iter.IsDone());
+ }
+
+ // We might be able to figure out some sort of optimizations here, but they
+ // would have to depend on having a correct aPrevSibling and a correct next
+ // sibling. For example, we can probably avoid reframing if none of
+ // aFrame, aPrevSibling, and next sibling are table pseudo-frames. But it
+ // doesn't seem worth it to worry about that for now, especially since we
+ // in fact do not have a reliable aPrevSibling, nor any next sibling, in
+ // this method.
+
+ // aItems might have changed, so recheck the parent type thing. In fact,
+ // it might be empty, so recheck that too.
+ if (aItems.IsEmpty()) {
+ return false;
+ }
+
+ if (!aItems.AllWantParentType(parentType)) {
+ // Reframing aFrame->GetContent() is good enough, since the content of
+ // table pseudo-frames is the ancestor content.
+ RecreateFramesForContent(aFrame->GetContent(), true,
+ REMOVE_FOR_RECONSTRUCTION, nullptr);
+ return true;
+ }
+ }
+
+ // Now we have several cases involving {ib} splits. Put them all in a
+ // do/while with breaks to take us to the "go and reconstruct" code.
+ do {
+ if (IsInlineFrame(aFrame)) {
+ if (aItems.AreAllItemsInline()) {
+ // We can just put the kids in.
+ return false;
+ }
+
+ if (!IsFramePartOfIBSplit(aFrame)) {
+ // Need to go ahead and reconstruct.
+ break;
+ }
+
+ // Now we're adding kids including some blocks to an inline part of an
+ // {ib} split. If we plan to call AppendFrames, and don't have a next
+ // sibling for the new frames, and our parent is the last continuation of
+ // the last part of the {ib} split, and the same is true of all our
+ // ancestor inlines (they have no following continuations and they're the
+ // last part of their {ib} splits and we'd be adding to the end for all
+ // of them), then AppendFrames will handle things for us. Bail out in
+ // that case.
+ if (aIsAppend && IsSafeToAppendToIBSplitInline(aFrame, nextSibling)) {
+ return false;
+ }
+
+ // Need to reconstruct.
+ break;
+ }
+
+ // Now we know we have a block parent. If it's not part of an
+ // ib-split, we're all set.
+ if (!IsFramePartOfIBSplit(aFrame)) {
+ return false;
+ }
+
+ // We're adding some kids to a block part of an {ib} split. If all the
+ // kids are blocks, we don't need to reconstruct.
+ if (aItems.AreAllItemsBlock()) {
+ return false;
+ }
+
+ // We might have some inline kids for this block. Just fall out of the
+ // loop and reconstruct.
+ } while (0);
+
+ // If we don't have a containing block, start with aFrame and look for one.
+ if (!aContainingBlock) {
+ aContainingBlock = aFrame;
+ }
+
+ // To find the right block to reframe, just walk up the tree until we find a
+ // frame that is:
+ // 1) Not part of an IB split
+ // 2) Not a pseudo-frame
+ // 3) Not an inline frame
+ // We're guaranteed to find one, since nsStyleContext::ApplyStyleFixups
+ // enforces that the root is display:none, display:table, or display:block.
+ // Note that walking up "too far" is OK in terms of correctness, even if it
+ // might be a little inefficient. This is why we walk out of all
+ // pseudo-frames -- telling which ones are or are not OK to walk out of is
+ // too hard (and I suspect that we do in fact need to walk out of all of
+ // them).
+ while (IsFramePartOfIBSplit(aContainingBlock) ||
+ aContainingBlock->IsInlineOutside() ||
+ aContainingBlock->StyleContext()->GetPseudo()) {
+ aContainingBlock = aContainingBlock->GetParent();
+ NS_ASSERTION(aContainingBlock,
+ "Must have non-inline, non-ib-split, non-pseudo frame as "
+ "root (or child of root, for a table root)!");
+ }
+
+ // Tell parent of the containing block to reformulate the
+ // entire block. This is painful and definitely not optimal
+ // but it will *always* get the right answer.
+
+ nsIContent *blockContent = aContainingBlock->GetContent();
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::WipeContainingBlock: blockContent=%p\n",
+ static_cast<void*>(blockContent));
+ }
+#endif
+ RecreateFramesForContent(blockContent, true, REMOVE_FOR_RECONSTRUCTION,
+ nullptr);
+ return true;
+}
+
+nsresult
+nsCSSFrameConstructor::ReframeContainingBlock(nsIFrame* aFrame,
+ RemoveFlags aFlags,
+ nsIContent** aDestroyedFramesFor)
+{
+
+#ifdef DEBUG
+ // ReframeContainingBlock is a NASTY routine, it causes terrible performance problems
+ // so I want to see when it is happening! Unfortunately, it is happening way to often because
+ // so much content on the web causes block-in-inline frame situations and we handle them
+ // very poorly
+ if (gNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ReframeContainingBlock frame=%p\n",
+ static_cast<void*>(aFrame));
+ }
+#endif
+
+ // XXXbz how exactly would we get here while isReflowing anyway? Should this
+ // whole test be ifdef DEBUG?
+ if (mPresShell->IsReflowLocked()) {
+ // don't ReframeContainingBlock, this will result in a crash
+ // if we remove a tree that's in reflow - see bug 121368 for testcase
+ NS_ERROR("Atemptted to nsCSSFrameConstructor::ReframeContainingBlock during a Reflow!!!");
+ return NS_OK;
+ }
+
+ // Get the first "normal" ancestor of the target frame.
+ nsIFrame* containingBlock = GetIBContainingBlockFor(aFrame);
+ if (containingBlock) {
+ // From here we look for the containing block in case the target
+ // frame is already a block (which can happen when an inline frame
+ // wraps some of its content in an anonymous block; see
+ // ConstructInline)
+
+ // NOTE: We used to get the FloatContainingBlock here, but it was often wrong.
+ // GetIBContainingBlock works much better and provides the correct container in all cases
+ // so GetFloatContainingBlock(aFrame) has been removed
+
+ // And get the containingBlock's content
+ nsCOMPtr<nsIContent> blockContent = containingBlock->GetContent();
+ if (blockContent) {
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf(" ==> blockContent=%p\n", static_cast<void*>(blockContent));
+ }
+#endif
+ return RecreateFramesForContent(blockContent, true, aFlags, aDestroyedFramesFor);
+ }
+ }
+
+ // If we get here, we're screwed!
+ return RecreateFramesForContent(mPresShell->GetDocument()->GetRootElement(),
+ true, aFlags, nullptr);
+}
+
+nsresult
+nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame)
+{
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ BeginUpdate();
+
+ nsFrameItems childItems;
+ nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+ // We don't have a parent frame with a pending binding constructor here,
+ // so no need to worry about ordering of the kids' constructors with it.
+ // Pass null for the PendingBinding.
+ ProcessChildren(state, aFrame->GetContent(), aFrame->StyleContext(),
+ aFrame, false, childItems, false,
+ nullptr);
+
+ aFrame->SetInitialChildList(kPrincipalList, childItems);
+
+ EndUpdate();
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIContent* container = aFrame->GetContent();
+ nsIContent* child = container->GetFirstChild();
+ if (child) {
+ accService->ContentRangeInserted(mPresShell, container, child, nullptr);
+ }
+ }
+#endif
+
+ // call XBL constructors after the frames are created
+ mPresShell->GetDocument()->BindingManager()->ProcessAttachedQueue();
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItem methods //
+//////////////////////////////////////////////////////////
+bool
+nsCSSFrameConstructor::
+FrameConstructionItem::IsWhitespace(nsFrameConstructorState& aState) const
+{
+ NS_PRECONDITION(aState.mCreatingExtraFrames ||
+ !mContent->GetPrimaryFrame(), "How did that happen?");
+ if (!mIsText) {
+ return false;
+ }
+ mContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
+ NS_REFRAME_IF_WHITESPACE);
+ return mContent->TextIsOnlyWhitespace();
+}
+
+//////////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItemList methods //
+//////////////////////////////////////////////////////////////
+void
+nsCSSFrameConstructor::FrameConstructionItemList::
+AdjustCountsForItem(FrameConstructionItem* aItem, int32_t aDelta)
+{
+ NS_PRECONDITION(aDelta == 1 || aDelta == -1, "Unexpected delta");
+ mItemCount += aDelta;
+ if (aItem->mIsAllInline) {
+ mInlineCount += aDelta;
+ }
+ if (aItem->mIsBlock) {
+ mBlockCount += aDelta;
+ }
+ if (aItem->mIsLineParticipant) {
+ mLineParticipantCount += aDelta;
+ }
+ mDesiredParentCounts[aItem->DesiredParentType()] += aDelta;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItemList::Iterator methods //
+////////////////////////////////////////////////////////////////////////
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipItemsWantingParentType(ParentType aParentType)
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ while (item().DesiredParentType() == aParentType) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipItemsNotWantingParentType(ParentType aParentType)
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ while (item().DesiredParentType() != aParentType) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Note: we implement -webkit-box & -webkit-inline-box using
+// nsFlexContainerFrame, but we use different rules for what gets wrapped in an
+// anonymous flex item.
+bool
+nsCSSFrameConstructor::FrameConstructionItem::
+ NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+ bool aIsWebkitBox)
+{
+ if (mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) {
+ // This will be an inline non-replaced box.
+ return true;
+ }
+
+ if (aIsWebkitBox) {
+ if (mStyleContext->StyleDisplay()->IsInlineOutsideStyle()) {
+ // In a -webkit-box, all inline-level content gets wrapped in anon item.
+ return true;
+ }
+ if (!(mFCData->mBits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
+ aState.GetGeometricParent(mStyleContext->StyleDisplay(), nullptr)) {
+ // We're abspos or fixedpos, which means we'll spawn a placeholder which
+ // (because our container is a -webkit-box) we'll need to wrap in an
+ // anonymous flex item. So, we just treat _this_ frame as if _it_ needs
+ // to be wrapped in an anonymous flex item, and then when we spawn the
+ // placeholder, it'll end up in the right spot.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipItemsThatNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState,
+ bool aIsWebkitBox)
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ while (item().NeedsAnonFlexOrGridItem(aState, aIsWebkitBox)) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipItemsThatDontNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState,
+ bool aIsWebkitBox)
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ while (!(item().NeedsAnonFlexOrGridItem(aState, aIsWebkitBox))) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipItemsNotWantingRubyParent()
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ while (!IsRubyParentType(item().DesiredParentType())) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::SkipWhitespace(nsFrameConstructorState& aState)
+{
+ NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
+ NS_PRECONDITION(item().IsWhitespace(aState), "Not pointing to whitespace?");
+ do {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ } while (item().IsWhitespace(aState));
+
+ return false;
+}
+
+void
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::AppendItemToList(FrameConstructionItemList& aTargetList)
+{
+ NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
+ NS_PRECONDITION(!IsDone(), "should not be done");
+
+ FrameConstructionItem* item = mCurrent;
+ Next();
+ item->remove();
+ aTargetList.mItems.insertBack(item);
+
+ mList.AdjustCountsForItem(item, -1);
+ aTargetList.AdjustCountsForItem(item, 1);
+}
+
+void
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::AppendItemsToList(const Iterator& aEnd,
+ FrameConstructionItemList& aTargetList)
+{
+ NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
+ NS_PRECONDITION(&mList == &aEnd.mList, "End iterator for some other list?");
+
+ // We can't just move our guts to the other list if it already has
+ // some information or if we're not moving our entire list.
+ if (!AtStart() || !aEnd.IsDone() || !aTargetList.IsEmpty() ||
+ !aTargetList.mUndisplayedItems.IsEmpty()) {
+ do {
+ AppendItemToList(aTargetList);
+ } while (*this != aEnd);
+ return;
+ }
+
+ // Move our entire list of items into the empty target list.
+ aTargetList.mItems = Move(mList.mItems);
+
+ // Copy over the various counters
+ aTargetList.mInlineCount = mList.mInlineCount;
+ aTargetList.mBlockCount = mList.mBlockCount;
+ aTargetList.mLineParticipantCount = mList.mLineParticipantCount;
+ aTargetList.mItemCount = mList.mItemCount;
+ memcpy(aTargetList.mDesiredParentCounts, mList.mDesiredParentCounts,
+ sizeof(aTargetList.mDesiredParentCounts));
+
+ // Swap out undisplayed item arrays, before we nuke the array on our end
+ aTargetList.mUndisplayedItems.SwapElements(mList.mUndisplayedItems);
+
+ // reset mList
+ mList.~FrameConstructionItemList();
+ new (&mList) FrameConstructionItemList();
+
+ // Point ourselves to aEnd, as advertised
+ SetToEnd();
+ NS_POSTCONDITION(*this == aEnd, "How did that happen?");
+}
+
+void
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::InsertItem(FrameConstructionItem* aItem)
+{
+ if (IsDone()) {
+ mList.mItems.insertBack(aItem);
+ } else {
+ // Just insert the item before us. There's no magic here.
+ mCurrent->setPrevious(aItem);
+ }
+ mList.AdjustCountsForItem(aItem, 1);
+
+ NS_POSTCONDITION(aItem->getNext() == mCurrent, "How did that happen?");
+}
+
+void
+nsCSSFrameConstructor::FrameConstructionItemList::
+Iterator::DeleteItemsTo(const Iterator& aEnd)
+{
+ NS_PRECONDITION(&mList == &aEnd.mList, "End iterator for some other list?");
+ NS_PRECONDITION(*this != aEnd, "Shouldn't be at aEnd yet");
+
+ do {
+ NS_ASSERTION(!IsDone(), "Ran off end of list?");
+ FrameConstructionItem* item = mCurrent;
+ Next();
+ item->remove();
+ mList.AdjustCountsForItem(item, -1);
+ delete item;
+ } while (*this != aEnd);
+}
diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h
new file mode 100644
index 000000000..7d1b8d42f
--- /dev/null
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -0,0 +1,2136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * construction of a frame tree that is nearly isomorphic to the content
+ * tree and updating of that tree in response to dynamic changes
+ */
+
+#ifndef nsCSSFrameConstructor_h___
+#define nsCSSFrameConstructor_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RestyleManagerBase.h"
+#include "mozilla/RestyleManagerHandle.h"
+
+#include "nsCOMPtr.h"
+#include "nsILayoutHistoryState.h"
+#include "nsQuoteList.h"
+#include "nsCounterManager.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsFrameManager.h"
+#include "ScrollbarStyles.h"
+
+struct nsFrameItems;
+class nsStyleContext;
+struct nsStyleDisplay;
+struct nsGenConInitializer;
+
+class nsContainerFrame;
+class nsFirstLineFrame;
+class nsICSSAnonBoxPseudo;
+class nsIDocument;
+class nsPageContentFrame;
+struct PendingBinding;
+class nsGenericDOMDataNode;
+
+class nsFrameConstructorState;
+
+namespace mozilla {
+
+namespace dom {
+
+class FlattenedChildIterator;
+
+} // namespace dom
+} // namespace mozilla
+
+class nsCSSFrameConstructor : public nsFrameManager
+{
+public:
+ typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
+ typedef mozilla::dom::Element Element;
+
+ friend class mozilla::RestyleManager;
+ friend class mozilla::RestyleManagerBase;
+ friend class mozilla::ServoRestyleManager;
+
+ nsCSSFrameConstructor(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ ~nsCSSFrameConstructor(void) {
+ NS_ASSERTION(mUpdateCount == 0, "Dying in the middle of our own update?");
+ }
+
+ // get the alternate text for a content node
+ static void GetAlternateTextFor(nsIContent* aContent,
+ nsIAtom* aTag, // content object's tag
+ nsXPIDLString& aAltText);
+
+private:
+ nsCSSFrameConstructor(const nsCSSFrameConstructor& aCopy) = delete;
+ nsCSSFrameConstructor& operator=(const nsCSSFrameConstructor& aCopy) = delete;
+
+public:
+ mozilla::RestyleManagerHandle RestyleManager() const
+ { return mPresShell->GetPresContext()->RestyleManager(); }
+
+ nsIFrame* ConstructRootFrame();
+
+ nsresult ReconstructDocElementHierarchy();
+
+ // Create frames for content nodes that are marked as needing frames. This
+ // should be called before ProcessPendingRestyles.
+ // Note: It's the caller's responsibility to make sure to wrap a
+ // CreateNeededFrames call in a view update batch and a script blocker.
+ void CreateNeededFrames();
+
+private:
+ void CreateNeededFrames(nsIContent* aContent);
+
+ enum Operation {
+ CONTENTAPPEND,
+ CONTENTINSERT
+ };
+
+ // aChild is the child being inserted for inserts, and the first
+ // child being appended for appends.
+ bool MaybeConstructLazily(Operation aOperation,
+ nsIContent* aContainer,
+ nsIContent* aChild);
+
+ // Issues a single ContentInserted for each child of aContainer in the range
+ // [aStartChild, aEndChild).
+ void IssueSingleInsertNofications(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ bool aAllowLazyConstruction);
+
+ /**
+ * Data that represents an insertion point for some child content.
+ */
+ struct InsertionPoint
+ {
+ InsertionPoint()
+ : mParentFrame(nullptr), mContainer(nullptr), mMultiple(false) {}
+ InsertionPoint(nsContainerFrame* aParentFrame, nsIContent* aContainer,
+ bool aMultiple = false)
+ : mParentFrame(aParentFrame), mContainer(aContainer),
+ mMultiple(aMultiple) {}
+ /**
+ * The parent frame to use if the inserted children needs to create
+ * frame(s). May be null, which signals that we shouldn't try to
+ * create frames for the inserted children; either because there are
+ * no parent frame or because there are multiple insertion points and
+ * we will call IssueSingleInsertNofications for each child instead.
+ * mContainer should not be used when mParentFrame is null.
+ */
+ nsContainerFrame* mParentFrame;
+ /**
+ * The flattened tree parent for the inserted children.
+ * It's undefined if mParentFrame is null.
+ */
+ nsIContent* mContainer;
+ /**
+ * If true then there are multiple insertion points, which means consumers
+ * should insert children individually into the node's flattened tree parent.
+ */
+ bool mMultiple;
+ };
+ /**
+ * Checks if the children of aContainer in the range [aStartChild, aEndChild)
+ * can be inserted/appended to one insertion point together. If so, returns
+ * that insertion point. If not, returns with InsertionPoint.mFrame == nullptr
+ * and issues single ContentInserted calls for each child.
+ * aEndChild = nullptr indicates that we are dealing with an append.
+ */
+ InsertionPoint GetRangeInsertionPoint(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ bool aAllowLazyConstruction);
+
+ // Returns true if parent was recreated due to frameset child, false otherwise.
+ bool MaybeRecreateForFrameset(nsIFrame* aParentFrame,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild);
+
+public:
+ /**
+ * Lazy frame construction is controlled by the aAllowLazyConstruction bool
+ * parameter of nsCSSFrameConstructor::ContentAppended/Inserted. It is true
+ * for all inserts/appends as passed from the presshell, except for the
+ * insert of the root element, which is always non-lazy. Even if the
+ * aAllowLazyConstruction passed to ContentAppended/Inserted is true we still
+ * may not be able to construct lazily, so we call MaybeConstructLazily.
+ * MaybeConstructLazily does not allow lazy construction if any of the
+ * following are true:
+ * -we are in chrome
+ * -the container is in a native anonymous subtree
+ * -the container is XUL
+ * -is any of the appended/inserted nodes are XUL or editable
+ * -(for inserts) the child is anonymous. In the append case this function
+ * must not be called with anonymous children.
+ * The XUL and chrome checks are because XBL bindings only get applied at
+ * frame construction time and some things depend on the bindings getting
+ * attached synchronously. The editable checks are because the editor seems
+ * to expect frames to be constructed synchronously.
+ *
+ * If MaybeConstructLazily returns false we construct as usual, but if it
+ * returns true then it adds NODE_NEEDS_FRAME bits to the newly
+ * inserted/appended nodes and adds NODE_DESCENDANTS_NEED_FRAMES bits to the
+ * container and up along the parent chain until it hits the root or another
+ * node with that bit set. Then it posts a restyle event to ensure that a
+ * flush happens to construct those frames.
+ *
+ * When the flush happens the presshell calls
+ * nsCSSFrameConstructor::CreateNeededFrames. CreateNeededFrames follows any
+ * nodes with NODE_DESCENDANTS_NEED_FRAMES set down the content tree looking
+ * for nodes with NODE_NEEDS_FRAME set. It calls ContentAppended for any runs
+ * of nodes with NODE_NEEDS_FRAME set that are at the end of their childlist,
+ * and ContentRangeInserted for any other runs that aren't.
+ *
+ * If a node is removed from the document then we don't bother unsetting any
+ * of the lazy bits that might be set on it, its descendants, or any of its
+ * ancestor nodes because that is a slow operation, the work might be wasted
+ * if another node gets inserted in its place, and we can clear the bits
+ * quicker by processing the content tree from top down the next time we call
+ * CreateNeededFrames. (We do clear the bits when BindToTree is called on any
+ * nsIContent; so any nodes added to the document will not have any lazy bits
+ * set.)
+ */
+
+ // If aAllowLazyConstruction is true then frame construction of the new
+ // children can be done lazily.
+ nsresult ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ bool aAllowLazyConstruction);
+
+ // If aAllowLazyConstruction is true then frame construction of the new child
+ // can be done lazily.
+ nsresult ContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsILayoutHistoryState* aFrameState,
+ bool aAllowLazyConstruction);
+
+ // Like ContentInserted but handles inserting the children of aContainer in
+ // the range [aStartChild, aEndChild). aStartChild must be non-null.
+ // aEndChild may be null to indicate the range includes all kids after
+ // aStartChild. If aAllowLazyConstruction is true then frame construction of
+ // the new children can be done lazily. It is only allowed to be true when
+ // inserting a single node.
+ nsresult ContentRangeInserted(nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ nsILayoutHistoryState* aFrameState,
+ bool aAllowLazyConstruction);
+
+ enum RemoveFlags {
+ REMOVE_CONTENT, REMOVE_FOR_RECONSTRUCTION, REMOVE_DESTROY_FRAMES };
+ /**
+ * Recreate or destroy frames for aChild in aContainer.
+ * aFlags == REMOVE_CONTENT means aChild has been removed from the document.
+ * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the
+ * frames later.
+ * In both the above cases, this method will in some cases try to reconstruct
+ * the frames (aDidReconstruct is then set to true), it's just that in the
+ * former case aChild isn't in the document so no frames will be created for
+ * it. Ancestors may have been reframed though.
+ * aFlags == REMOVE_DESTROY_FRAMES is the same as REMOVE_FOR_RECONSTRUCTION
+ * except it will never try to reconstruct frames. Instead, the caller is
+ * responsible for doing that, on the content returned in aDestroyedFramesFor.
+ * The layout frame state is guarranted to be captured for the removed frames
+ * only when aFlags == REMOVE_DESTROY_FRAMES, otherwise it will only be
+ * captured if we reconstructed frames for an ancestor.
+ */
+ nsresult ContentRemoved(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aOldNextSibling,
+ RemoveFlags aFlags,
+ bool* aDidReconstruct,
+ nsIContent** aDestroyedFramesFor = nullptr);
+
+ nsresult CharacterDataChanged(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo);
+
+ // If aContent is a text node that has been optimized away due to being
+ // whitespace next to a block boundary (or for some other reason), stop
+ // doing that and create a frame for it if it should have one. This recreates
+ // frames so be careful (although this should not change actual layout).
+ // Returns the frame for aContent if there is one.
+ nsIFrame* EnsureFrameForTextNode(nsGenericDOMDataNode* aContent);
+
+ // generate the child frames and process bindings
+ nsresult GenerateChildFrames(nsContainerFrame* aFrame);
+
+ // Should be called when a frame is going to be destroyed and
+ // WillDestroyFrameTree hasn't been called yet.
+ void NotifyDestroyingFrame(nsIFrame* aFrame);
+
+ void BeginUpdate();
+ void EndUpdate();
+ void RecalcQuotesAndCounters();
+
+ // Called when any counter style is changed.
+ void NotifyCounterStylesAreDirty();
+
+ // Gets called when the presshell is destroying itself and also
+ // when we tear down our frame tree to reconstruct it
+ void WillDestroyFrameTree();
+
+ /**
+ * Destroy the frames for aContent. Note that this may destroy frames
+ * for an ancestor instead - aDestroyedFramesFor contains the content node
+ * where frames were actually destroyed (which should be used in the
+ * ContentInserted call to recreate frames). The frame tree state
+ * is captured before the frames are destroyed and can be retrieved using
+ * GetLastCapturedLayoutHistoryState().
+ */
+ void DestroyFramesFor(nsIContent* aContent,
+ nsIContent** aDestroyedFramesFor);
+
+ // Request to create a continuing frame. This method never returns null.
+ nsIFrame* CreateContinuingFrame(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ bool aIsFluid = true);
+
+ // Copy over fixed frames from aParentFrame's prev-in-flow
+ nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame);
+
+ /**
+ * Get the XBL insertion point for aChild in aContainer.
+ */
+ InsertionPoint GetInsertionPoint(nsIContent* aContainer, nsIContent* aChild);
+
+ nsresult CreateListBoxContent(nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevFrame,
+ nsIContent* aChild,
+ nsIFrame** aResult,
+ bool aIsAppend);
+
+ // GetInitialContainingBlock() is deprecated in favor of GetRootElementFrame();
+ // nsIFrame* GetInitialContainingBlock() { return mRootElementFrame; }
+ // This returns the outermost frame for the root element
+ nsContainerFrame* GetRootElementFrame() { return mRootElementFrame; }
+ // This returns the frame for the root element that does not
+ // have a psuedo-element style
+ nsIFrame* GetRootElementStyleFrame() { return mRootElementStyleFrame; }
+ nsIFrame* GetPageSequenceFrame() { return mPageSequenceFrame; }
+
+ // Get the frame that is the parent of the root element.
+ nsContainerFrame* GetDocElementContainingBlock()
+ { return mDocElementContainingBlock; }
+
+ /**
+ * Return the layout history state that was captured in the last
+ * ContentRemoved / RecreateFramesForContent call.
+ */
+ nsILayoutHistoryState* GetLastCapturedLayoutHistoryState()
+ {
+ return mTempFrameTreeState;
+ }
+
+private:
+ struct FrameConstructionItem;
+ class FrameConstructionItemList;
+
+ nsContainerFrame* ConstructPageFrame(nsIPresShell* aPresShell,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevPageFrame,
+ nsContainerFrame*& aCanvasFrame);
+
+ void InitAndRestoreFrame (const nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aNewFrame,
+ bool aAllowCounters = true);
+
+ // aState can be null if not available; it's used as an optimization.
+ // XXXbz IsValidSibling is the only caller that doesn't pass a state here!
+ already_AddRefed<nsStyleContext>
+ ResolveStyleContext(nsIFrame* aParentFrame,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState);
+ already_AddRefed<nsStyleContext>
+ ResolveStyleContext(nsIFrame* aParentFrame,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState);
+ already_AddRefed<nsStyleContext>
+ ResolveStyleContext(const InsertionPoint& aInsertion,
+ nsIContent* aChild,
+ nsFrameConstructorState* aState);
+ already_AddRefed<nsStyleContext>
+ ResolveStyleContext(nsStyleContext* aParentStyleContext,
+ nsIContent* aContent,
+ nsFrameConstructorState* aState);
+
+ // Add the frame construction items for the given aContent and aParentFrame
+ // to the list. This might add more than one item in some rare cases.
+ // If aSuppressWhiteSpaceOptimizations is true, optimizations that
+ // may suppress the construction of white-space-only text frames
+ // must be skipped for these items and items around them.
+ void AddFrameConstructionItems(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ bool aSuppressWhiteSpaceOptimizations,
+ const InsertionPoint& aInsertion,
+ FrameConstructionItemList& aItems);
+
+ // Helper method for AddFrameConstructionItems etc.
+ // Unsets the need-frame/restyle bits on aContent.
+ // return true iff we should attempt to create frames for aContent.
+ bool ShouldCreateItemsForChild(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame);
+
+ // Helper method for AddFrameConstructionItems etc.
+ // Make sure ShouldCreateItemsForChild() returned true before calling this.
+ void DoAddFrameConstructionItems(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsContainerFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren,
+ FrameConstructionItemList& aItems);
+
+ // Construct the frames for the document element. This can return null if the
+ // document element is display:none, or if the document element has a
+ // not-yet-loaded XBL binding, or if it's an SVG element that's not <svg>.
+ nsIFrame* ConstructDocElementFrame(Element* aDocElement,
+ nsILayoutHistoryState* aFrameState);
+
+ // Set up our mDocElementContainingBlock correctly for the given root
+ // content.
+ void SetUpDocElementContainingBlock(nsIContent* aDocElement);
+
+ /**
+ * CreateAttributeContent creates a single content/frame combination for an
+ * |attr(foo)| generated content.
+ *
+ * @param aParentContent the parent content for the generated content
+ * @param aParentFrame the parent frame for the generated frame
+ * @param aAttrNamespace the namespace of the attribute in question
+ * @param aAttrName the localname of the attribute
+ * @param aStyleContext the style context to use
+ * @param aGeneratedContent the array of generated content to append the
+ * created content to.
+ * @param [out] aNewContent the content node we create
+ * @param [out] aNewFrame the new frame we create
+ */
+ nsresult CreateAttributeContent(nsIContent* aParentContent,
+ nsIFrame* aParentFrame,
+ int32_t aAttrNamespace,
+ nsIAtom* aAttrName,
+ nsStyleContext* aStyleContext,
+ nsCOMArray<nsIContent>& aGeneratedContent,
+ nsIContent** aNewContent,
+ nsIFrame** aNewFrame);
+
+ /**
+ * Create a text node containing the given string. If aText is non-null
+ * then we also set aText to the returned node.
+ */
+ already_AddRefed<nsIContent> CreateGenConTextNode(nsFrameConstructorState& aState,
+ const nsString& aString,
+ RefPtr<nsTextNode>* aText,
+ nsGenConInitializer* aInitializer);
+
+ /**
+ * Create a content node for the given generated content style.
+ * The caller takes care of making it SetIsNativeAnonymousRoot, binding it
+ * to the document, and creating frames for it.
+ * @param aParentContent is the node that has the before/after style
+ * @param aStyleContext is the 'before' or 'after' pseudo-element
+ * style context
+ * @param aContentIndex is the index of the content item to create
+ */
+ already_AddRefed<nsIContent> CreateGeneratedContent(nsFrameConstructorState& aState,
+ nsIContent* aParentContent,
+ nsStyleContext* aStyleContext,
+ uint32_t aContentIndex);
+
+ // aFrame may be null; this method doesn't use it directly in any case.
+ void CreateGeneratedContentItem(nsFrameConstructorState& aState,
+ nsContainerFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ CSSPseudoElementType aPseudoElement,
+ FrameConstructionItemList& aItems);
+
+ // This method can change aFrameList: it can chop off the beginning and put
+ // it in aParentFrame while putting the remainder into a ib-split sibling of
+ // aParentFrame. aPrevSibling must be the frame after which aFrameList is to
+ // be placed on aParentFrame's principal child list. It may be null if
+ // aFrameList is being added at the beginning of the child list.
+ nsresult AppendFramesToParent(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameList,
+ nsIFrame* aPrevSibling,
+ bool aIsRecursiveCall = false);
+
+ // BEGIN TABLE SECTION
+ /**
+ * Construct a table wrapper frame. This is the FrameConstructionData
+ * callback used for the job.
+ */
+ nsIFrame* ConstructTable(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * FrameConstructionData callback for constructing table rows and row groups.
+ */
+ nsIFrame* ConstructTableRowOrRowGroup(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * FrameConstructionData callback used for constructing table columns.
+ */
+ nsIFrame* ConstructTableCol(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * FrameConstructionData callback used for constructing table cells.
+ */
+ nsIFrame* ConstructTableCell(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+private:
+ /* An enum of possible parent types for anonymous table or ruby object
+ construction */
+ enum ParentType {
+ eTypeBlock = 0, /* This includes all non-table-related frames */
+ eTypeRow,
+ eTypeRowGroup,
+ eTypeColGroup,
+ eTypeTable,
+ eTypeRuby,
+ eTypeRubyBase,
+ eTypeRubyBaseContainer,
+ eTypeRubyText,
+ eTypeRubyTextContainer,
+ eParentTypeCount
+ };
+
+ /* 4 bits is enough to handle our ParentType values */
+#define FCDATA_PARENT_TYPE_OFFSET 28
+ /* Macro to get the desired parent type out of an mBits member of
+ FrameConstructionData */
+#define FCDATA_DESIRED_PARENT_TYPE(_bits) \
+ ParentType((_bits) >> FCDATA_PARENT_TYPE_OFFSET)
+ /* Macro to create FrameConstructionData bits out of a desired parent type */
+#define FCDATA_DESIRED_PARENT_TYPE_TO_BITS(_type) \
+ (((uint32_t)(_type)) << FCDATA_PARENT_TYPE_OFFSET)
+
+ /* Get the parent type that aParentFrame has. */
+ static ParentType GetParentType(nsIFrame* aParentFrame) {
+ return GetParentType(aParentFrame->GetType());
+ }
+
+ /* Get the parent type for the given nsIFrame type atom */
+ static ParentType GetParentType(nsIAtom* aFrameType);
+
+ static bool IsRubyParentType(ParentType aParentType) {
+ return (aParentType == eTypeRuby ||
+ aParentType == eTypeRubyBase ||
+ aParentType == eTypeRubyBaseContainer ||
+ aParentType == eTypeRubyText ||
+ aParentType == eTypeRubyTextContainer);
+ }
+
+ static bool IsTableParentType(ParentType aParentType) {
+ return (aParentType == eTypeTable ||
+ aParentType == eTypeRow ||
+ aParentType == eTypeRowGroup ||
+ aParentType == eTypeColGroup);
+ }
+
+ /* A constructor function that just creates an nsIFrame object. The caller
+ is responsible for initializing the object, adding it to frame lists,
+ constructing frames for the children, etc.
+
+ @param nsIPresShell the presshell whose arena should be used to allocate
+ the frame.
+ @param nsStyleContext the style context to use for the frame. */
+ typedef nsIFrame* (* FrameCreationFunc)(nsIPresShell*, nsStyleContext*);
+ typedef nsContainerFrame* (* ContainerFrameCreationFunc)(nsIPresShell*, nsStyleContext*);
+ typedef nsBlockFrame* (* BlockFrameCreationFunc)(nsIPresShell*, nsStyleContext*);
+
+ /* A function that can be used to get a FrameConstructionData. Such
+ a function is allowed to return null.
+
+ @param nsIContent the node for which the frame is being constructed.
+ @param nsStyleContext the style context to be used for the frame.
+ */
+ struct FrameConstructionData;
+ typedef const FrameConstructionData*
+ (* FrameConstructionDataGetter)(Element*, nsStyleContext*);
+
+ /* A constructor function that's used for complicated construction tasks.
+ This is expected to create the new frame, initialize it, add whatever
+ needs to be added to aFrameItems (XXXbz is that really necessary? Could
+ caller add? Might there be cases when the returned frame or its
+ placeholder is not the thing that ends up in aFrameItems? If not, would
+ it be safe to do the add into the frame construction state after
+ processing kids? Look into this as a followup!), process children as
+ needed, etc. It is NOT expected to deal with setting the frame on the
+ content.
+
+ @param aState the frame construction state to use.
+ @param aItem the frame construction item to use
+ @param aParentFrame the frame to set as the parent of the
+ newly-constructed frame.
+ @param aStyleDisplay the display struct from aItem's mStyleContext
+ @param aFrameItems the frame list to add the new frame (or its
+ placeholder) to.
+ @return the frame that was constructed. This frame is what the caller
+ will set as the frame on the content. Guaranteed non-null.
+ */
+ typedef nsIFrame*
+ (nsCSSFrameConstructor::* FrameFullConstructor)(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ /* Bits that modify the way a FrameConstructionData is handled */
+
+ /* If the FCDATA_SKIP_FRAMESET bit is set, then the frame created should not
+ be set as the primary frame on the content node. This should only be used
+ in very rare cases when we create more than one frame for a given content
+ node. */
+#define FCDATA_SKIP_FRAMESET 0x1
+ /* If the FCDATA_FUNC_IS_DATA_GETTER bit is set, then the mFunc of the
+ FrameConstructionData is a getter function that can be used to get the
+ actual FrameConstructionData to use. */
+#define FCDATA_FUNC_IS_DATA_GETTER 0x2
+ /* If the FCDATA_FUNC_IS_FULL_CTOR bit is set, then the FrameConstructionData
+ has an mFullConstructor. In this case, there is no relevant mData or
+ mFunc */
+#define FCDATA_FUNC_IS_FULL_CTOR 0x4
+ /* If FCDATA_DISALLOW_OUT_OF_FLOW is set, do not allow the frame to
+ float or be absolutely positioned. This can also be used with
+ FCDATA_FUNC_IS_FULL_CTOR to indicate what the full-constructor
+ function will do. */
+#define FCDATA_DISALLOW_OUT_OF_FLOW 0x8
+ /* If FCDATA_FORCE_NULL_ABSPOS_CONTAINER is set, make sure to push a
+ null absolute containing block before processing children for this
+ frame. If this is not set, the frame will be pushed as the
+ absolute containing block as needed, based on its style */
+#define FCDATA_FORCE_NULL_ABSPOS_CONTAINER 0x10
+ /* If FCDATA_WRAP_KIDS_IN_BLOCKS is set, the inline kids of the frame
+ will be wrapped in blocks. This is only usable for MathML at the
+ moment. */
+#define FCDATA_WRAP_KIDS_IN_BLOCKS 0x20
+ /* If FCDATA_SUPPRESS_FRAME is set, no frame should be created for the
+ content. If this bit is set, nothing else in the struct needs to be
+ set. */
+#define FCDATA_SUPPRESS_FRAME 0x40
+ /* If FCDATA_MAY_NEED_SCROLLFRAME is set, the new frame should be wrapped in
+ a scrollframe if its overflow type so requires. */
+#define FCDATA_MAY_NEED_SCROLLFRAME 0x80
+#ifdef MOZ_XUL
+ /* If FCDATA_IS_POPUP is set, the new frame is a XUL popup frame. These need
+ some really weird special handling. */
+#define FCDATA_IS_POPUP 0x100
+#endif /* MOZ_XUL */
+ /* If FCDATA_SKIP_ABSPOS_PUSH is set, don't push this frame as an
+ absolute containing block, no matter what its style says. */
+#define FCDATA_SKIP_ABSPOS_PUSH 0x200
+ /* If FCDATA_DISALLOW_GENERATED_CONTENT is set, then don't allow generated
+ content when processing kids of this frame. This should not be used with
+ FCDATA_FUNC_IS_FULL_CTOR */
+#define FCDATA_DISALLOW_GENERATED_CONTENT 0x400
+ /* If FCDATA_IS_TABLE_PART is set, then the frame is some sort of
+ table-related thing and we should not attempt to fetch a table-cell parent
+ for it if it's inside another table-related frame. */
+#define FCDATA_IS_TABLE_PART 0x800
+ /* If FCDATA_IS_INLINE is set, then the frame is a non-replaced CSS
+ inline box. */
+#define FCDATA_IS_INLINE 0x1000
+ /* If FCDATA_IS_LINE_PARTICIPANT is set, the frame is something that will
+ return true for IsFrameOfType(nsIFrame::eLineParticipant) */
+#define FCDATA_IS_LINE_PARTICIPANT 0x2000
+ /* If FCDATA_IS_LINE_BREAK is set, the frame is something that will
+ induce a line break boundary before and after itself. */
+#define FCDATA_IS_LINE_BREAK 0x4000
+ /* If FCDATA_ALLOW_BLOCK_STYLES is set, allow block styles when processing
+ children. This should not be used with FCDATA_FUNC_IS_FULL_CTOR. */
+#define FCDATA_ALLOW_BLOCK_STYLES 0x8000
+ /* If FCDATA_USE_CHILD_ITEMS is set, then use the mChildItems in the relevant
+ FrameConstructionItem instead of trying to process the content's children.
+ This can be used with or without FCDATA_FUNC_IS_FULL_CTOR.
+ The child items might still need table pseudo processing. */
+#define FCDATA_USE_CHILD_ITEMS 0x10000
+ /* If FCDATA_FORCED_NON_SCROLLABLE_BLOCK is set, then this block
+ would have been scrollable but has been forced to be
+ non-scrollable due to being in a paginated context. */
+#define FCDATA_FORCED_NON_SCROLLABLE_BLOCK 0x20000
+ /* If FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, then create a
+ block formatting context wrapper around the kids of this frame
+ using the FrameConstructionData's mPseudoAtom for its anonymous
+ box type. */
+#define FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS 0x40000
+ /* If FCDATA_IS_SVG_TEXT is set, then this text frame is a descendant of
+ an SVG text frame. */
+#define FCDATA_IS_SVG_TEXT 0x80000
+ /**
+ * display:contents
+ */
+#define FCDATA_IS_CONTENTS 0x100000
+ /**
+ * When FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, this bit says
+ * if we should create a grid/flex/columnset container instead of
+ * a block wrapper when the styles says so.
+ */
+#define FCDATA_ALLOW_GRID_FLEX_COLUMNSET 0x200000
+
+ /* Structure representing information about how a frame should be
+ constructed. */
+ struct FrameConstructionData {
+ // Flag bits that can modify the way the construction happens
+ uint32_t mBits;
+ // We have exactly one of three types of functions, so use a union for
+ // better cache locality for the ones that aren't pointer-to-member. That
+ // one needs to be separate, because we can't cast between it and the
+ // others and hence wouldn't be able to initialize the union without a
+ // constructor and all the resulting generated code. See documentation
+ // above for FrameCreationFunc, FrameConstructionDataGetter, and
+ // FrameFullConstructor to see what the functions would do.
+ union Func {
+ FrameCreationFunc mCreationFunc;
+ FrameConstructionDataGetter mDataGetter;
+ } mFunc;
+ FrameFullConstructor mFullConstructor;
+ // For cases when FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, the
+ // anonymous box type to use for that wrapper.
+ nsICSSAnonBoxPseudo * const * const mAnonBoxPseudo;
+ };
+
+ /* Structure representing a mapping of an atom to a FrameConstructionData.
+ This can be used with non-static atoms, assuming that the nsIAtom* is
+ stored somewhere that this struct can point to (that is, a static
+ nsIAtom*) and that it's allocated before the struct is ever used. */
+ struct FrameConstructionDataByTag {
+ // Pointer to nsIAtom* is used because we want to initialize this
+ // statically, so before our atom tables are set up.
+ const nsIAtom * const * const mTag;
+ const FrameConstructionData mData;
+ };
+
+ /* Structure representing a mapping of an integer to a
+ FrameConstructionData. There are no magic integer values here. */
+ struct FrameConstructionDataByInt {
+ /* Could be used for display or whatever else */
+ const int32_t mInt;
+ const FrameConstructionData mData;
+ };
+
+ struct FrameConstructionDataByDisplay {
+#ifdef DEBUG
+ const mozilla::StyleDisplay mDisplay;
+#endif
+ const FrameConstructionData mData;
+ };
+
+#ifdef DEBUG
+#define FCDATA_FOR_DISPLAY(_display, _fcdata) \
+ { _display, _fcdata }
+#else
+#define FCDATA_FOR_DISPLAY(_display, _fcdata) \
+ { _fcdata }
+#endif
+
+ /* Structure that has a FrameConstructionData and style context pseudo-type
+ for a table pseudo-frame */
+ struct PseudoParentData {
+ const FrameConstructionData mFCData;
+ nsICSSAnonBoxPseudo * const * const mPseudoType;
+ };
+ /* Array of such structures that we use to properly construct table
+ pseudo-frames as needed */
+ static const PseudoParentData sPseudoParentData[eParentTypeCount];
+
+ /* A function that takes an integer, content, style context, and array of
+ FrameConstructionDataByInts and finds the appropriate frame construction
+ data to use and returns it. This can return null if none of the integers
+ match or if the matching integer has a FrameConstructionDataGetter that
+ returns null. */
+ static const FrameConstructionData*
+ FindDataByInt(int32_t aInt, Element* aElement,
+ nsStyleContext* aStyleContext,
+ const FrameConstructionDataByInt* aDataPtr,
+ uint32_t aDataLength);
+
+ /* A function that takes a tag, content, style context, and array of
+ FrameConstructionDataByTags and finds the appropriate frame construction
+ data to use and returns it. This can return null if none of the tags
+ match or if the matching tag has a FrameConstructionDataGetter that
+ returns null. */
+ static const FrameConstructionData*
+ FindDataByTag(nsIAtom* aTag, Element* aElement,
+ nsStyleContext* aStyleContext,
+ const FrameConstructionDataByTag* aDataPtr,
+ uint32_t aDataLength);
+
+ /* A class representing a list of FrameConstructionItems */
+ class FrameConstructionItemList final {
+ public:
+ FrameConstructionItemList() :
+ mInlineCount(0),
+ mBlockCount(0),
+ mLineParticipantCount(0),
+ mItemCount(0),
+ mLineBoundaryAtStart(false),
+ mLineBoundaryAtEnd(false),
+ mParentHasNoXBLChildren(false),
+ mTriedConstructingFrames(false)
+ {
+ memset(mDesiredParentCounts, 0, sizeof(mDesiredParentCounts));
+ }
+
+ ~FrameConstructionItemList() {
+ while (FrameConstructionItem* item = mItems.popFirst()) {
+ delete item;
+ }
+
+ // Create the undisplayed entries for our mUndisplayedItems, if any, but
+ // only if we have tried constructing frames for this item list. If we
+ // haven't, then we're just throwing it away and will probably try again.
+ if (!mUndisplayedItems.IsEmpty() && mTriedConstructingFrames) {
+ // We could store the frame manager in a member, but just
+ // getting it off the style context is not too bad.
+ nsFrameManager *mgr =
+ mUndisplayedItems[0].mStyleContext->PresContext()->FrameManager();
+ for (uint32_t i = 0; i < mUndisplayedItems.Length(); ++i) {
+ UndisplayedItem& item = mUndisplayedItems[i];
+ mgr->SetUndisplayedContent(item.mContent, item.mStyleContext);
+ }
+ }
+ }
+
+ void SetLineBoundaryAtStart(bool aBoundary) { mLineBoundaryAtStart = aBoundary; }
+ void SetLineBoundaryAtEnd(bool aBoundary) { mLineBoundaryAtEnd = aBoundary; }
+ void SetParentHasNoXBLChildren(bool aHasNoXBLChildren) {
+ mParentHasNoXBLChildren = aHasNoXBLChildren;
+ }
+ void SetTriedConstructingFrames() { mTriedConstructingFrames = true; }
+ bool HasLineBoundaryAtStart() { return mLineBoundaryAtStart; }
+ bool HasLineBoundaryAtEnd() { return mLineBoundaryAtEnd; }
+ bool ParentHasNoXBLChildren() { return mParentHasNoXBLChildren; }
+ bool IsEmpty() const { return mItems.isEmpty(); }
+ bool AnyItemsNeedBlockParent() const { return mLineParticipantCount != 0; }
+ bool AreAllItemsInline() const { return mInlineCount == mItemCount; }
+ bool AreAllItemsBlock() const { return mBlockCount == mItemCount; }
+ bool AllWantParentType(ParentType aDesiredParentType) const {
+ return mDesiredParentCounts[aDesiredParentType] == mItemCount;
+ }
+
+ // aSuppressWhiteSpaceOptimizations is true if optimizations that
+ // skip constructing whitespace frames for this item or items
+ // around it cannot be performed.
+ // Also, the return value is always non-null, thanks to infallible 'new'.
+ FrameConstructionItem* AppendItem(const FrameConstructionData* aFCData,
+ nsIContent* aContent,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ PendingBinding* aPendingBinding,
+ already_AddRefed<nsStyleContext>&& aStyleContext,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren)
+ {
+ FrameConstructionItem* item =
+ new FrameConstructionItem(aFCData, aContent, aTag, aNameSpaceID,
+ aPendingBinding, aStyleContext,
+ aSuppressWhiteSpaceOptimizations,
+ aAnonChildren);
+ mItems.insertBack(item);
+ ++mItemCount;
+ ++mDesiredParentCounts[item->DesiredParentType()];
+ return item;
+ }
+
+ // Arguments are the same as AppendItem().
+ FrameConstructionItem* PrependItem(const FrameConstructionData* aFCData,
+ nsIContent* aContent,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ PendingBinding* aPendingBinding,
+ already_AddRefed<nsStyleContext>&& aStyleContext,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren)
+ {
+ FrameConstructionItem* item =
+ new FrameConstructionItem(aFCData, aContent, aTag, aNameSpaceID,
+ aPendingBinding, aStyleContext,
+ aSuppressWhiteSpaceOptimizations,
+ aAnonChildren);
+ mItems.insertFront(item);
+ ++mItemCount;
+ ++mDesiredParentCounts[item->DesiredParentType()];
+ return item;
+ }
+
+ void AppendUndisplayedItem(nsIContent* aContent,
+ nsStyleContext* aStyleContext) {
+ mUndisplayedItems.AppendElement(UndisplayedItem(aContent, aStyleContext));
+ }
+
+ void InlineItemAdded() { ++mInlineCount; }
+ void BlockItemAdded() { ++mBlockCount; }
+ void LineParticipantItemAdded() { ++mLineParticipantCount; }
+
+ class Iterator {
+ public:
+ explicit Iterator(FrameConstructionItemList& aList)
+ : mCurrent(aList.mItems.getFirst())
+ , mList(aList)
+ {}
+ Iterator(const Iterator& aOther) :
+ mCurrent(aOther.mCurrent),
+ mList(aOther.mList)
+ {}
+
+ bool operator==(const Iterator& aOther) const {
+ MOZ_ASSERT(&mList == &aOther.mList, "Iterators for different lists?");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const Iterator& aOther) const {
+ return !(*this == aOther);
+ }
+ Iterator& operator=(const Iterator& aOther) {
+ MOZ_ASSERT(&mList == &aOther.mList, "Iterators for different lists?");
+ mCurrent = aOther.mCurrent;
+ return *this;
+ }
+
+ FrameConstructionItemList* List() {
+ return &mList;
+ }
+
+ FrameConstructionItem& item() {
+ MOZ_ASSERT(!IsDone(), "Should have checked IsDone()!");
+ return *mCurrent;
+ }
+
+ const FrameConstructionItem& item() const {
+ MOZ_ASSERT(!IsDone(), "Should have checked IsDone()!");
+ return *mCurrent;
+ }
+
+ bool IsDone() const { return mCurrent == nullptr; }
+ bool AtStart() const { return mCurrent == mList.mItems.getFirst(); }
+ void Next() {
+ NS_ASSERTION(!IsDone(), "Should have checked IsDone()!");
+ mCurrent = mCurrent->getNext();
+ }
+ void Prev() {
+ NS_ASSERTION(!AtStart(), "Should have checked AtStart()!");
+ mCurrent = mCurrent ? mCurrent->getPrevious() : mList.mItems.getLast();
+ }
+ void SetToEnd() { mCurrent = nullptr; }
+
+ // Skip over all items that want the given parent type. Return whether
+ // the iterator is done after doing that. The iterator must not be done
+ // when this is called.
+ inline bool SkipItemsWantingParentType(ParentType aParentType);
+
+ // Skip over all items that want a parent type different from the given
+ // one. Return whether the iterator is done after doing that. The
+ // iterator must not be done when this is called.
+ inline bool SkipItemsNotWantingParentType(ParentType aParentType);
+
+ // Skip over non-replaced inline frames and positioned frames.
+ // Return whether the iterator is done after doing that.
+ // The iterator must not be done when this is called.
+ inline bool SkipItemsThatNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState,
+ bool aIsWebkitBox);
+
+ // Skip to the first frame that is a non-replaced inline or is
+ // positioned. Return whether the iterator is done after doing that.
+ // The iterator must not be done when this is called.
+ inline bool SkipItemsThatDontNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState,
+ bool aIsWebkitBox);
+
+ // Skip over all items that do not want a ruby parent. Return whether
+ // the iterator is done after doing that. The iterator must not be done
+ // when this is called.
+ inline bool SkipItemsNotWantingRubyParent();
+
+ // Skip over whitespace. Return whether the iterator is done after doing
+ // that. The iterator must not be done, and must be pointing to a
+ // whitespace item when this is called.
+ inline bool SkipWhitespace(nsFrameConstructorState& aState);
+
+ // Remove the item pointed to by this iterator from its current list and
+ // Append it to aTargetList. This iterator is advanced to point to the
+ // next item in its list. aIter must not be done. aTargetList must not be
+ // the list this iterator is iterating over..
+ void AppendItemToList(FrameConstructionItemList& aTargetList);
+
+ // As above, but moves all items starting with this iterator until we
+ // get to aEnd; the item pointed to by aEnd is not stolen. This method
+ // might have optimizations over just looping and doing StealItem for
+ // some special cases. After this method returns, this iterator will
+ // point to the item aEnd points to now; aEnd is not modified.
+ // aTargetList must not be the list this iterator is iterating over.
+ void AppendItemsToList(const Iterator& aEnd,
+ FrameConstructionItemList& aTargetList);
+
+ // Insert aItem in this iterator's list right before the item pointed to
+ // by this iterator. After the insertion, this iterator will continue to
+ // point to the item it now points to (the one just after the
+ // newly-inserted item). This iterator is allowed to be done; in that
+ // case this call just appends the given item to the list.
+ void InsertItem(FrameConstructionItem* aItem);
+
+ // Delete the items between this iterator and aEnd, including the item
+ // this iterator currently points to but not including the item pointed
+ // to by aEnd. When this returns, this iterator will point to the same
+ // item as aEnd. This iterator must not equal aEnd when this method is
+ // called.
+ void DeleteItemsTo(const Iterator& aEnd);
+
+ private:
+ FrameConstructionItem* mCurrent;
+ FrameConstructionItemList& mList;
+ };
+
+ private:
+ struct UndisplayedItem {
+ UndisplayedItem(nsIContent* aContent, nsStyleContext* aStyleContext) :
+ mContent(aContent), mStyleContext(aStyleContext)
+ {}
+
+ nsIContent * const mContent;
+ RefPtr<nsStyleContext> mStyleContext;
+ };
+
+ // Adjust our various counts for aItem being added or removed. aDelta
+ // should be either +1 or -1 depending on which is happening.
+ void AdjustCountsForItem(FrameConstructionItem* aItem, int32_t aDelta);
+
+ mozilla::LinkedList<FrameConstructionItem> mItems;
+ uint32_t mInlineCount;
+ uint32_t mBlockCount;
+ uint32_t mLineParticipantCount;
+ uint32_t mItemCount;
+ uint32_t mDesiredParentCounts[eParentTypeCount];
+ // True if there is guaranteed to be a line boundary before the
+ // frames created by these items
+ bool mLineBoundaryAtStart;
+ // True if there is guaranteed to be a line boundary after the
+ // frames created by these items
+ bool mLineBoundaryAtEnd;
+ // True if the parent is guaranteed to have no XBL anonymous children
+ bool mParentHasNoXBLChildren;
+ // True if we have tried constructing frames from this list
+ bool mTriedConstructingFrames;
+
+ nsTArray<UndisplayedItem> mUndisplayedItems;
+ };
+
+ typedef FrameConstructionItemList::Iterator FCItemIterator;
+
+ /* A struct representing an item for which frames might need to be
+ * constructed. This contains all the information needed to construct the
+ * frame other than the parent frame and whatever would be stored in the
+ * frame constructor state. */
+ struct FrameConstructionItem final
+ : public mozilla::LinkedListElement<FrameConstructionItem> {
+ FrameConstructionItem(const FrameConstructionData* aFCData,
+ nsIContent* aContent,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ PendingBinding* aPendingBinding,
+ already_AddRefed<nsStyleContext>& aStyleContext,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren) :
+ mFCData(aFCData), mContent(aContent), mTag(aTag),
+ mPendingBinding(aPendingBinding), mStyleContext(aStyleContext),
+ mNameSpaceID(aNameSpaceID),
+ mSuppressWhiteSpaceOptimizations(aSuppressWhiteSpaceOptimizations),
+ mIsText(false), mIsGeneratedContent(false),
+ mIsAnonymousContentCreatorContent(false),
+ mIsRootPopupgroup(false), mIsAllInline(false), mIsBlock(false),
+ mHasInlineEnds(false), mIsPopup(false),
+ mIsLineParticipant(false), mIsForSVGAElement(false)
+ {
+ if (aAnonChildren) {
+ NS_ASSERTION(!(mFCData->mBits & FCDATA_FUNC_IS_FULL_CTOR) ||
+ mFCData->mFullConstructor ==
+ &nsCSSFrameConstructor::ConstructInline,
+ "This is going to fail");
+ NS_ASSERTION(!(mFCData->mBits & FCDATA_USE_CHILD_ITEMS),
+ "nsIAnonymousContentCreator::CreateAnonymousContent "
+ "implementations should not output a list where the "
+ "items have children in this case");
+ mAnonChildren.SwapElements(*aAnonChildren);
+ }
+ }
+ ~FrameConstructionItem() {
+ if (mIsGeneratedContent) {
+ mContent->UnbindFromTree();
+ NS_RELEASE(mContent);
+ }
+ }
+
+ ParentType DesiredParentType() {
+ return FCDATA_DESIRED_PARENT_TYPE(mFCData->mBits);
+ }
+
+ // Indicates whether (when in a flex or grid container) this item needs
+ // to be wrapped in an anonymous block. (Note that we implement
+ // -webkit-box/-webkit-inline-box using our standard flexbox frame class,
+ // but we use different rules for what gets wrapped. The aIsWebkitBox
+ // parameter here tells us whether to use those different rules.)
+ bool NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+ bool aIsWebkitBox);
+
+ // Don't call this unless the frametree really depends on the answer!
+ // Especially so for generated content, where we don't want to reframe
+ // things.
+ bool IsWhitespace(nsFrameConstructorState& aState) const;
+
+ bool IsLineBoundary() const {
+ return mIsBlock || (mFCData->mBits & FCDATA_IS_LINE_BREAK);
+ }
+
+ // Child frame construction items.
+ FrameConstructionItemList mChildItems;
+
+ // ContentInfo list for children that have yet to have
+ // FrameConstructionItem objects created for them. This exists because
+ // AddFrameConstructionItemsInternal needs a valid frame, but in the case
+ // that nsIAnonymousContentCreator::CreateAnonymousContent returns items
+ // that have their own children (so we have a tree of ContentInfo objects
+ // rather than a flat list) we don't yet have a frame to provide to
+ // AddFrameConstructionItemsInternal in order to create the items for the
+ // grandchildren. That prevents FrameConstructionItems from being created
+ // for these grandchildren (and any descendants that they may have),
+ // otherwise they could have been added to the mChildItems member of their
+ // parent FrameConstructionItem. As it is, the grandchildren ContentInfo
+ // list has to be stored in this mAnonChildren member in order to delay
+ // construction of the FrameConstructionItems for the grandchildren until
+ // a frame has been created for their parent item.
+ nsTArray<nsIAnonymousContentCreator::ContentInfo> mAnonChildren;
+
+ // The FrameConstructionData to use.
+ const FrameConstructionData* mFCData;
+ // The nsIContent node to use when initializing the new frame.
+ nsIContent* mContent;
+ // The XBL-resolved tag name to use for frame construction.
+ nsIAtom* mTag;
+ // The PendingBinding for this frame construction item, if any. May be
+ // null. We maintain a list of PendingBindings in the frame construction
+ // state in the order in which AddToAttachedQueue should be called on them:
+ // depth-first, post-order traversal order. Since we actually traverse the
+ // DOM in a mix of breadth-first and depth-first, it is the responsibility
+ // of whoever constructs FrameConstructionItem kids of a given
+ // FrameConstructionItem to push its mPendingBinding as the current
+ // insertion point before doing so and pop it afterward.
+ PendingBinding* mPendingBinding;
+ // The style context to use for creating the new frame.
+ RefPtr<nsStyleContext> mStyleContext;
+ // The XBL-resolved namespace to use for frame construction.
+ int32_t mNameSpaceID;
+ // Whether optimizations to skip constructing textframes around
+ // this content need to be suppressed.
+ bool mSuppressWhiteSpaceOptimizations:1;
+ // Whether this is a text content item.
+ bool mIsText:1;
+ // Whether this is a generated content container.
+ // If it is, mContent is a strong pointer.
+ bool mIsGeneratedContent:1;
+ // Whether this is an item for nsIAnonymousContentCreator content.
+ bool mIsAnonymousContentCreatorContent:1;
+ // Whether this is an item for the root popupgroup.
+ bool mIsRootPopupgroup:1;
+ // Whether construction from this item will create only frames that are
+ // IsInlineOutside() in the principal child list. This is not precise, but
+ // conservative: if true the frames will really be inline, whereas if false
+ // they might still all be inline.
+ bool mIsAllInline:1;
+ // Whether construction from this item will create only frames that are
+ // IsBlockOutside() in the principal child list. This is not precise, but
+ // conservative: if true the frames will really be blocks, whereas if false
+ // they might still be blocks (and in particular, out-of-flows that didn't
+ // find a containing block).
+ bool mIsBlock:1;
+ // Whether construction from this item will give leading and trailing
+ // inline frames. This is equal to mIsAllInline, except for inline frame
+ // items, where it's always true, whereas mIsAllInline might be false due
+ // to {ib} splits.
+ bool mHasInlineEnds:1;
+ // Whether construction from this item will create a popup that needs to
+ // go into the global popup items.
+ bool mIsPopup:1;
+ // Whether this item should be treated as a line participant
+ bool mIsLineParticipant:1;
+ // Whether this item is for an SVG <a> element
+ bool mIsForSVGAElement:1;
+
+ private:
+ FrameConstructionItem(const FrameConstructionItem& aOther) = delete; /* not implemented */
+ };
+
+ /**
+ * Function to create the anonymous flex or grid items that we need.
+ * If aParentFrame is not a nsFlexContainerFrame or nsGridContainerFrame then
+ * this method is a NOP.
+ * @param aItems the child frame construction items before pseudo creation
+ * @param aParentFrame the parent frame
+ */
+ void CreateNeededAnonFlexOrGridItems(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ enum RubyWhitespaceType
+ {
+ eRubyNotWhitespace,
+ eRubyInterLevelWhitespace,
+ // Includes inter-base and inter-annotation whitespace
+ eRubyInterLeafWhitespace,
+ eRubyInterSegmentWhitespace
+ };
+
+ /**
+ * Function to compute the whitespace type according to the display
+ * values of the previous and the next elements.
+ */
+ static inline RubyWhitespaceType ComputeRubyWhitespaceType(
+ mozilla::StyleDisplay aPrevDisplay, mozilla::StyleDisplay aNextDisplay);
+
+ /**
+ * Function to interpret the type of whitespace between
+ * |aStartIter| and |aEndIter|.
+ */
+ static inline RubyWhitespaceType InterpretRubyWhitespace(
+ nsFrameConstructorState& aState,
+ const FCItemIterator& aStartIter, const FCItemIterator& aEndIter);
+
+ /**
+ * Function to wrap consecutive misparented inline content into
+ * a ruby base box or a ruby text box.
+ */
+ void WrapItemsInPseudoRubyLeafBox(FCItemIterator& aIter,
+ nsStyleContext* aParentStyle,
+ nsIContent* aParentContent);
+
+ /**
+ * Function to wrap consecutive misparented items
+ * into a ruby level container.
+ */
+ inline void WrapItemsInPseudoRubyLevelContainer(
+ nsFrameConstructorState& aState, FCItemIterator& aIter,
+ nsStyleContext* aParentStyle, nsIContent* aParentContent);
+
+ /**
+ * Function to trim leading and trailing whitespaces.
+ */
+ inline void TrimLeadingAndTrailingWhitespaces(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems);
+
+ /**
+ * Function to create internal ruby boxes.
+ */
+ inline void CreateNeededPseudoInternalRubyBoxes(
+ nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems, nsIFrame* aParentFrame);
+
+ /**
+ * Function to create the pseudo intermediate containers we need.
+ * @param aItems the child frame construction items before pseudo creation
+ * @param aParentFrame the parent frame we're creating pseudos for
+ */
+ inline void CreateNeededPseudoContainers(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ /**
+ * Function to wrap consecutive items into a pseudo parent.
+ */
+ inline void WrapItemsInPseudoParent(nsIContent* aParentContent,
+ nsStyleContext* aParentStyle,
+ ParentType aWrapperType,
+ FCItemIterator& aIter,
+ const FCItemIterator& aEndIter);
+
+ /**
+ * Function to create the pseudo siblings we need.
+ */
+ inline void CreateNeededPseudoSiblings(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ /**
+ * Function to adjust aParentFrame to deal with captions.
+ * @param aParentFrame the frame we think should be the parent. This will be
+ * adjusted to point to the right parent frame.
+ * @param aFCData the FrameConstructionData that would be used for frame
+ * construction.
+ * @param aStyleContext the style context for aChildContent
+ */
+ // XXXbz this function should really go away once we rework pseudo-frame
+ // handling to be better. This should simply be part of the job of
+ // GetGeometricParent, and stuff like the frameitems and parent frame should
+ // be kept track of in the state...
+ void AdjustParentFrame(nsContainerFrame** aParentFrame,
+ const FrameConstructionData* aFCData,
+ nsStyleContext* aStyleContext);
+
+ // END TABLE SECTION
+
+protected:
+ static nsIFrame* CreatePlaceholderFrameFor(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsStyleContext* aParentStyle,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevInFlow,
+ nsFrameState aTypeBit);
+
+ static nsIFrame* CreateBackdropFrameFor(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame);
+
+private:
+ // ConstructSelectFrame puts the new frame in aFrameItems and
+ // handles the kids of the select.
+ nsIFrame* ConstructSelectFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ // ConstructFieldSetFrame puts the new frame in aFrameItems and
+ // handles the kids of the fieldset
+ nsIFrame* ConstructFieldSetFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ // ConstructDetailsFrame puts the new frame in aFrameItems and
+ // handles the kids of the details.
+ nsIFrame* ConstructDetailsFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameItems& aFrameItems);
+
+ // aParentFrame might be null. If it is, that means it was an
+ // inline frame.
+ static const FrameConstructionData* FindTextData(nsIFrame* aParentFrame);
+
+ void ConstructTextFrame(const FrameConstructionData* aData,
+ nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ nsFrameItems& aFrameItems);
+
+ // If aPossibleTextContent is a text node and doesn't have a frame, append a
+ // frame construction item for it to aItems.
+ void AddTextItemIfNeeded(nsFrameConstructorState& aState,
+ const InsertionPoint& aInsertion,
+ nsIContent* aPossibleTextContent,
+ FrameConstructionItemList& aItems);
+
+ // If aParentContent's child aContent is a text node and
+ // doesn't have a frame, try to create a frame for it.
+ void ReframeTextIfNeeded(nsIContent* aParentContent,
+ nsIContent* aContent);
+
+ void AddPageBreakItem(nsIContent* aContent,
+ nsStyleContext* aMainStyleContext,
+ FrameConstructionItemList& aItems);
+
+ // Function to find FrameConstructionData for aElement. Will return
+ // null if aElement is not HTML.
+ // aParentFrame might be null. If it is, that means it was an
+ // inline frame.
+ static const FrameConstructionData* FindHTMLData(Element* aContent,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsIFrame* aParentFrame,
+ nsStyleContext* aStyleContext);
+ // HTML data-finding helper functions
+ static const FrameConstructionData*
+ FindImgData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindImgControlData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindInputData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindObjectData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindCanvasData(Element* aElement, nsStyleContext* aStyleContext);
+
+ /* Construct a frame from the given FrameConstructionItem. This function
+ will handle adding the frame to frame lists, processing children, setting
+ the frame as the primary frame for the item's content, and so forth.
+
+ @param aItem the FrameConstructionItem to use.
+ @param aState the frame construction state to use.
+ @param aParentFrame the frame to set as the parent of the
+ newly-constructed frame.
+ @param aFrameItems the frame list to add the new frame (or its
+ placeholder) to.
+ */
+ void ConstructFrameFromItemInternal(FrameConstructionItem& aItem,
+ nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems);
+
+ // possible flags for AddFrameConstructionItemInternal's aFlags argument
+ /* Allow xbl:base to affect the tag/namespace used. */
+#define ITEM_ALLOW_XBL_BASE 0x1
+ /* Allow page-break before and after items to be created if the
+ style asks for them. */
+#define ITEM_ALLOW_PAGE_BREAK 0x2
+ /* The item is a generated content item. */
+#define ITEM_IS_GENERATED_CONTENT 0x4
+ /* The item is within an SVG text block frame. */
+#define ITEM_IS_WITHIN_SVG_TEXT 0x8
+ /* The item allows items to be created for SVG <textPath> children. */
+#define ITEM_ALLOWS_TEXT_PATH_CHILD 0x10
+ /* The item is content created by an nsIAnonymousContentCreator frame */
+#define ITEM_IS_ANONYMOUSCONTENTCREATOR_CONTENT 0x20
+ // The guts of AddFrameConstructionItems
+ // aParentFrame might be null. If it is, that means it was an
+ // inline frame.
+ void AddFrameConstructionItemsInternal(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ bool aSuppressWhiteSpaceOptimizations,
+ nsStyleContext* aStyleContext,
+ uint32_t aFlags,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren,
+ FrameConstructionItemList& aItems);
+
+ /**
+ * Construct frames for the given item list and parent frame, and put the
+ * resulting frames in aFrameItems.
+ */
+ void ConstructFramesFromItemList(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems);
+ void ConstructFramesFromItem(nsFrameConstructorState& aState,
+ FCItemIterator& aItem,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems);
+ static bool AtLineBoundary(FCItemIterator& aIter);
+
+ nsresult CreateAnonymousFrames(nsFrameConstructorState& aState,
+ nsIContent* aParent,
+ nsContainerFrame* aParentFrame,
+ PendingBinding* aPendingBinding,
+ nsFrameItems& aChildItems);
+
+ nsresult GetAnonymousContent(nsIContent* aParent,
+ nsIFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonContent);
+
+//MathML Mod - RBS
+ /**
+ * Takes the frames in aBlockItems and wraps them in a new anonymous block
+ * frame whose content is aContent and whose parent will be aParentFrame.
+ * The anonymous block is added to aNewItems and aBlockItems is cleared.
+ */
+ void FlushAccumulatedBlock(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aBlockItems,
+ nsFrameItems& aNewItems);
+
+ // Function to find FrameConstructionData for aContent. Will return
+ // null if aContent is not MathML.
+ static const FrameConstructionData* FindMathMLData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsStyleContext* aStyleContext);
+
+ // Function to find FrameConstructionData for aContent. Will return
+ // null if aContent is not XUL.
+ static const FrameConstructionData* FindXULTagData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsStyleContext* aStyleContext);
+ // XUL data-finding helper functions and structures
+#ifdef MOZ_XUL
+ static const FrameConstructionData*
+ FindPopupGroupData(Element* aElement, nsStyleContext* aStyleContext);
+ // sXULTextBoxData used for both labels and descriptions
+ static const FrameConstructionData sXULTextBoxData;
+ static const FrameConstructionData*
+ FindXULLabelData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindXULDescriptionData(Element* aElement, nsStyleContext* aStyleContext);
+#ifdef XP_MACOSX
+ static const FrameConstructionData*
+ FindXULMenubarData(Element* aElement, nsStyleContext* aStyleContext);
+#endif /* XP_MACOSX */
+ static const FrameConstructionData*
+ FindXULListBoxBodyData(Element* aElement, nsStyleContext* aStyleContext);
+ static const FrameConstructionData*
+ FindXULListItemData(Element* aElement, nsStyleContext* aStyleContext);
+#endif /* MOZ_XUL */
+
+ // Function to find FrameConstructionData for aContent using one of the XUL
+ // display types. Will return null if aDisplay doesn't have a XUL display
+ // type. This function performs no other checks, so should only be called if
+ // we know for sure that the content is not something that should get a frame
+ // constructed by tag.
+ static const FrameConstructionData*
+ FindXULDisplayData(const nsStyleDisplay* aDisplay,
+ Element* aElement,
+ nsStyleContext* aStyleContext);
+
+ /**
+ * Constructs an outer frame, an anonymous child that wraps its real
+ * children, and its descendant frames. This is used by both ConstructOuterSVG
+ * and ConstructMarker, which both want an anonymous block child for their
+ * children to go in to.
+ */
+ nsContainerFrame* ConstructFrameWithAnonymousChild(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aFrameItems,
+ ContainerFrameCreationFunc aConstructor,
+ ContainerFrameCreationFunc aInnerConstructor,
+ nsICSSAnonBoxPseudo* aInnerPseudo,
+ bool aCandidateRootFrame);
+
+ /**
+ * Construct an nsSVGOuterSVGFrame.
+ */
+ nsIFrame* ConstructOuterSVG(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * Construct an nsSVGMarkerFrame.
+ */
+ nsIFrame* ConstructMarker(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ static const FrameConstructionData* FindSVGData(Element* aElement,
+ nsIAtom* aTag,
+ int32_t aNameSpaceID,
+ nsIFrame* aParentFrame,
+ bool aIsWithinSVGText,
+ bool aAllowsTextPathChild,
+ nsStyleContext* aStyleContext);
+
+ /* Not static because it does PropagateScrollToViewport. If this
+ changes, make this static */
+ const FrameConstructionData*
+ FindDisplayData(const nsStyleDisplay* aDisplay, Element* aElement,
+ nsStyleContext* aStyleContext);
+
+ /**
+ * Construct a scrollable block frame
+ */
+ nsIFrame* ConstructScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * Construct a scrollable block frame using the given block frame creation
+ * function.
+ */
+ nsIFrame* ConstructScrollableBlockWithConstructor(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems,
+ BlockFrameCreationFunc aConstructor);
+
+ /**
+ * Construct a non-scrollable block frame
+ */
+ nsIFrame* ConstructNonScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * Construct a non-scrollable block frame using the given block frame creation
+ * function.
+ */
+ nsIFrame* ConstructNonScrollableBlockWithConstructor(
+ nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems,
+ BlockFrameCreationFunc aConstructor);
+
+ /**
+ * This adds FrameConstructionItem objects to aItemsToConstruct for the
+ * anonymous content returned by an nsIAnonymousContentCreator::
+ * CreateAnonymousContent implementation.
+ */
+ void AddFCItemsForAnonymousContent(
+ nsFrameConstructorState& aState,
+ nsContainerFrame* aFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
+ FrameConstructionItemList& aItemsToConstruct,
+ uint32_t aExtraFlags = 0);
+
+ /**
+ * Construct the frames for the children of aContent. "children" is defined
+ * as "whatever FlattenedChildIterator returns for aContent". This means we're
+ * basically operating on children in the "flattened tree" per sXBL/XBL2.
+ * This method will also handle constructing ::before, ::after,
+ * ::first-letter, and ::first-line frames, as needed and if allowed.
+ *
+ * If the parent is a float containing block, this method will handle pushing
+ * it as the float containing block in aState (so there's no need for callers
+ * to push it themselves).
+ *
+ * @param aState the frame construction state
+ * @param aContent the content node whose children need frames
+ * @param aStyleContext the style context for aContent
+ * @param aParentFrame the frame to use as the parent frame for the new in-flow
+ * kids. Note that this must be its own content insertion frame, but
+ * need not be be the primary frame for aContent. This frame will be
+ * pushed as the float containing block, as needed. aFrame is also
+ * used to find the parent style context for the kids' style contexts
+ * (not necessary aFrame's style context).
+ * @param aCanHaveGeneratedContent Whether to allow :before and
+ * :after styles on the parent.
+ * @param aFrameItems the list in which we should place the in-flow children
+ * @param aAllowBlockStyles Whether to allow first-letter and first-line
+ * styles on the parent.
+ * @param aPendingBinding Make sure to push this into aState before doing any
+ * child item construction.
+ * @param aPossiblyLeafFrame if non-null, this should be used for the isLeaf
+ * test and the anonymous content creation. If null, aFrame will be
+ * used.
+ */
+ void ProcessChildren(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame* aParentFrame,
+ const bool aCanHaveGeneratedContent,
+ nsFrameItems& aFrameItems,
+ const bool aAllowBlockStyles,
+ PendingBinding* aPendingBinding,
+ nsIFrame* aPossiblyLeafFrame = nullptr);
+
+ /**
+ * These two functions are used when we start frame creation from a non-root
+ * element. They should recreate the same state that we would have
+ * arrived at if we had built frames from the root frame to aFrame.
+ * Therefore, any calls to PushFloatContainingBlock and
+ * PushAbsoluteContainingBlock during frame construction should get
+ * corresponding logic in these functions.
+ */
+public:
+ enum ContainingBlockType {
+ ABS_POS,
+ FIXED_POS
+ };
+ nsContainerFrame* GetAbsoluteContainingBlock(nsIFrame* aFrame,
+ ContainingBlockType aType);
+ nsContainerFrame* GetFloatContainingBlock(nsIFrame* aFrame);
+
+private:
+ // Build a scroll frame:
+ // Calls BeginBuildingScrollFrame, InitAndRestoreFrame, and then FinishBuildingScrollFrame.
+ // @param aNewFrame the created scrollframe --- output only
+ // @param aParentFrame the geometric parent that the scrollframe will have.
+ void
+ BuildScrollFrame(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aContentStyle,
+ nsIFrame* aScrolledFrame,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame*& aNewFrame);
+
+ // Builds the initial ScrollFrame
+ already_AddRefed<nsStyleContext>
+ BeginBuildingScrollFrame(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsStyleContext* aContentStyle,
+ nsContainerFrame* aParentFrame,
+ nsIAtom* aScrolledPseudo,
+ bool aIsRoot,
+ nsContainerFrame*& aNewFrame);
+
+ // Completes the building of the scrollframe:
+ // Creates a view for the scrolledframe and makes it the child of the scrollframe.
+ void
+ FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame,
+ nsIFrame* aScrolledFrame);
+
+ // InitializeSelectFrame puts scrollFrame in aFrameItems if aBuildCombobox is false
+ // aBuildCombobox indicates if we are building a combobox that has a dropdown
+ // popup widget or not.
+ nsresult
+ InitializeSelectFrame(nsFrameConstructorState& aState,
+ nsContainerFrame* aScrollFrame,
+ nsContainerFrame* aScrolledFrame,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ bool aBuildCombobox,
+ PendingBinding* aPendingBinding,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * ReResolve style for aElement then recreate frames if required.
+ * Do nothing for other types of style changes, except for undisplayed nodes
+ * (display:none/contents) which will have their style context updated in the
+ * frame manager undisplayed maps.
+ * @return null if frames were recreated, the new style context otherwise
+ */
+ nsStyleContext* MaybeRecreateFramesForElement(Element* aElement);
+
+ /**
+ * Recreate frames for aContent.
+ * @param aContent the content to recreate frames for
+ * @param aAsyncInsert if true then a restyle event will be posted to handle
+ * the required ContentInserted call instead of doing it immediately.
+ * @param aFlags normally you want to pass REMOVE_FOR_RECONSTRUCTION here
+ * @param aDestroyedFramesFor if non-null, it will contain the content that
+ * was actually reframed - it may be different than aContent.
+ */
+ nsresult
+ RecreateFramesForContent(nsIContent* aContent,
+ bool aAsyncInsert,
+ RemoveFlags aFlags,
+ nsIContent** aDestroyedFramesFor);
+
+ // If removal of aFrame from the frame tree requires reconstruction of some
+ // containing block (either of aFrame or of its parent) due to {ib} splits or
+ // table pseudo-frames, recreate the relevant frame subtree. The return value
+ // indicates whether this happened. If this method returns true, *aResult is
+ // the return value of ReframeContainingBlock or RecreateFramesForContent. If
+ // this method returns false, the value of *aResult is not affected. aFrame
+ // and aResult must not be null. aFrame must be the result of a
+ // GetPrimaryFrame() call on a content node (which means its parent is also
+ // not null). If this method returns true, aDestroyedFramesFor contains the
+ // content that was reframed.
+ bool MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame,
+ RemoveFlags aFlags,
+ nsresult* aResult,
+ nsIContent** aDestroyedFramesFor);
+
+ nsIFrame* CreateContinuingOuterTableFrame(nsIPresShell* aPresShell,
+ nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ nsIFrame* CreateContinuingTableFrame(nsIPresShell* aPresShell,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ //----------------------------------------
+
+ // Methods support creating block frames and their children
+
+ already_AddRefed<nsStyleContext>
+ GetFirstLetterStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ already_AddRefed<nsStyleContext>
+ GetFirstLineStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ bool ShouldHaveFirstLetterStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ // Check whether a given block has first-letter style. Make sure to
+ // only pass in blocks! And don't pass in null either.
+ bool HasFirstLetterStyle(nsIFrame* aBlockFrame);
+
+ bool ShouldHaveFirstLineStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+
+ void ShouldHaveSpecialBlockStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool* aHaveFirstLetterStyle,
+ bool* aHaveFirstLineStyle);
+
+ // |aContentParentFrame| should be null if it's really the same as
+ // |aParentFrame|.
+ // @param aFrameItems where we want to put the block in case it's in-flow.
+ // @param aNewFrame an in/out parameter. On input it is the block to be
+ // constructed. On output it is reset to the outermost
+ // frame constructed (e.g. if we need to wrap the block in an
+ // nsColumnSetFrame.
+ // @param aParentFrame is the desired parent for the (possibly wrapped)
+ // block
+ // @param aContentParent is the parent the block would have if it
+ // were in-flow
+ // @param aPositionedFrameForAbsPosContainer if non-null, then the new
+ // block should be an abs-pos container and aPositionedFrameForAbsPosContainer
+ // is the frame whose style is making this block an abs-pos container.
+ // @param aPendingBinding the pending binding from this block's frame
+ // construction item.
+ void ConstructBlock(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame* aContentParentFrame,
+ nsStyleContext* aStyleContext,
+ nsContainerFrame** aNewFrame,
+ nsFrameItems& aFrameItems,
+ nsIFrame* aPositionedFrameForAbsPosContainer,
+ PendingBinding* aPendingBinding);
+
+ nsIFrame* ConstructInline(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * Create any additional {ib} siblings needed to contain aChildItems and put
+ * them in aSiblings.
+ *
+ * @param aState the frame constructor state
+ * @param aInitialInline is an already-existing inline frame that will be
+ * part of this {ib} split and come before everything
+ * in aSiblings.
+ * @param aIsPositioned true if aInitialInline is positioned.
+ * @param aChildItems is a child list starting with a block; this method
+ * assumes that the inline has already taken all the
+ * children it wants. When the method returns aChildItems
+ * will be empty.
+ * @param aSiblings the nsFrameItems to put the newly-created siblings into.
+ *
+ * This method is responsible for making any SetFrameIsIBSplit calls that are
+ * needed.
+ */
+ void CreateIBSiblings(nsFrameConstructorState& aState,
+ nsContainerFrame* aInitialInline,
+ bool aIsPositioned,
+ nsFrameItems& aChildItems,
+ nsFrameItems& aSiblings);
+
+ /**
+ * For an inline aParentItem, construct its list of child
+ * FrameConstructionItems and set its mIsAllInline flag appropriately.
+ */
+ void BuildInlineChildItems(nsFrameConstructorState& aState,
+ FrameConstructionItem& aParentItem,
+ bool aItemIsWithinSVGText,
+ bool aItemAllowsTextPathChild);
+
+ // Determine whether we need to wipe out what we just did and start over
+ // because we're doing something like adding block kids to an inline frame
+ // (and therefore need an {ib} split). aPrevSibling must be correct, even in
+ // aIsAppend cases. Passing aIsAppend false even when an append is happening
+ // is ok in terms of correctness, but can lead to unnecessary reframing. If
+ // aIsAppend is true, then the caller MUST call
+ // nsCSSFrameConstructor::AppendFramesToParent (as opposed to
+ // nsFrameManager::InsertFrames directly) to add the new frames.
+ // @return true if we reconstructed the containing block, false
+ // otherwise
+ bool WipeContainingBlock(nsFrameConstructorState& aState,
+ nsIFrame* aContainingBlock,
+ nsIFrame* aFrame,
+ FrameConstructionItemList& aItems,
+ bool aIsAppend,
+ nsIFrame* aPrevSibling);
+
+ nsresult ReframeContainingBlock(nsIFrame* aFrame,
+ RemoveFlags aFlags,
+ nsIContent** aReframeContent);
+
+ //----------------------------------------
+
+ // Methods support :first-letter style
+
+ void CreateFloatingLetterFrame(nsFrameConstructorState& aState,
+ nsIContent* aTextContent,
+ nsIFrame* aTextFrame,
+ nsContainerFrame* aParentFrame,
+ nsStyleContext* aStyleContext,
+ nsFrameItems& aResult);
+
+ void CreateLetterFrame(nsContainerFrame* aBlockFrame,
+ nsContainerFrame* aBlockContinuation,
+ nsIContent* aTextContent,
+ nsContainerFrame* aParentFrame,
+ nsFrameItems& aResult);
+
+ void WrapFramesInFirstLetterFrame(nsContainerFrame* aBlockFrame,
+ nsFrameItems& aBlockFrames);
+
+ /**
+ * Looks in the block aBlockFrame for a text frame that contains the
+ * first-letter of the block and creates the necessary first-letter frames
+ * and returns them in aLetterFrames.
+ *
+ * @param aBlockFrame the (first-continuation of) the block we are creating a
+ * first-letter frame for
+ * @param aBlockContinuation the current continuation of the block that we
+ * are looking in for a textframe with suitable
+ * contents for first-letter
+ * @param aParentFrame the current frame whose children we are looking at for
+ * a suitable first-letter textframe
+ * @param aParentFrameList the first child of aParentFrame
+ * @param aModifiedParent returns the parent of the textframe that contains
+ * the first-letter
+ * @param aTextFrame returns the textframe that had the first-letter
+ * @param aPrevFrame returns the previous sibling of aTextFrame
+ * @param aLetterFrames returns the frames that were created
+ */
+ void WrapFramesInFirstLetterFrame(nsContainerFrame* aBlockFrame,
+ nsContainerFrame* aBlockContinuation,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aParentFrameList,
+ nsContainerFrame** aModifiedParent,
+ nsIFrame** aTextFrame,
+ nsIFrame** aPrevFrame,
+ nsFrameItems& aLetterFrames,
+ bool* aStopLooking);
+
+ void RecoverLetterFrames(nsContainerFrame* aBlockFrame);
+
+ //
+ nsresult RemoveLetterFrames(nsIPresShell* aPresShell,
+ nsContainerFrame* aBlockFrame);
+
+ // Recursive helper for RemoveLetterFrames
+ nsresult RemoveFirstLetterFrames(nsIPresShell* aPresShell,
+ nsContainerFrame* aFrame,
+ nsContainerFrame* aBlockFrame,
+ bool* aStopLooking);
+
+ // Special remove method for those pesky floating first-letter frames
+ nsresult RemoveFloatingFirstLetterFrames(nsIPresShell* aPresShell,
+ nsIFrame* aBlockFrame);
+
+ // Capture state for the frame tree rooted at the frame associated with the
+ // content object, aContent
+ void CaptureStateForFramesOf(nsIContent* aContent,
+ nsILayoutHistoryState* aHistoryState);
+
+ //----------------------------------------
+
+ // Methods support :first-line style
+
+ // This method chops the initial inline-outside frames out of aFrameItems.
+ // If aLineFrame is non-null, it appends them to that frame. Otherwise, it
+ // creates a new line frame, sets the inline frames as its initial child
+ // list, and inserts that line frame at the front of what's left of
+ // aFrameItems. In both cases, the kids are reparented to the line frame.
+ // After this call, aFrameItems holds the frames that need to become kids of
+ // the block (possibly including line frames).
+ void WrapFramesInFirstLineFrame(nsFrameConstructorState& aState,
+ nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame,
+ nsFirstLineFrame* aLineFrame,
+ nsFrameItems& aFrameItems);
+
+ // Handle the case when a block with first-line style is appended to (by
+ // possibly calling WrapFramesInFirstLineFrame as needed).
+ void AppendFirstLineFrames(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aBlockFrame,
+ nsFrameItems& aFrameItems);
+
+ nsresult InsertFirstLineFrames(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsIFrame* aBlockFrame,
+ nsContainerFrame** aParentFrame,
+ nsIFrame* aPrevSibling,
+ nsFrameItems& aFrameItems);
+
+ /**
+ * Find the right frame to use for aContent when looking for sibling
+ * frames for aTargetContent. If aPrevSibling is true, this
+ * will look for last continuations, etc, as necessary. This calls
+ * IsValidSibling as needed; if that returns false it returns null.
+ *
+ * @param aContent the content to search for frames
+ * @param aTargetContent the content we're finding a sibling frame for
+ * @param aTargetContentDisplay the CSS display enum for aTargetContent if
+ * already known, UNSET_DISPLAY otherwise. It will be filled in
+ * if needed.
+ * @param aParentFrame the nearest ancestor frame, used internally for
+ * finding ::after / ::before frames
+ * @param aPrevSibling true if we're searching in reverse DOM order
+ */
+ nsIFrame* FindFrameForContentSibling(nsIContent* aContent,
+ nsIContent* aTargetContent,
+ mozilla::StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame,
+ bool aPrevSibling);
+
+ /**
+ * Find the frame for the content immediately preceding the one aIter
+ * points to, following continuations if necessary. aIter is passed by
+ * value on purpose, so as not to modify the caller's iterator.
+ *
+ * @param aIter should be positioned such that aIter.GetPreviousChild()
+ * is the first content to search for frames
+ * @param aTargetContent the content we're finding a sibling frame for
+ * @param aTargetContentDisplay the CSS display enum for aTargetContent if
+ * already known, UNSET_DISPLAY otherwise. It will be filled in
+ * if needed.
+ * @param aParentFrame the nearest ancestor frame, used inernally for
+ * finding ::after / ::before frames
+ */
+ nsIFrame* FindPreviousSibling(mozilla::dom::FlattenedChildIterator aIter,
+ nsIContent* aTargetContent,
+ mozilla::StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame);
+
+ /**
+ * Find the frame for the content node immediately following the one aIter
+ * points to, following continuations if necessary. aIter is passed by value
+ * on purpose, so as not to modify the caller's iterator.
+ *
+ * @param aIter should be positioned such that aIter.GetNextChild()
+ * is the first content to search for frames
+ * @param aTargetContent the content we're finding a sibling frame for
+ * @param aTargetContentDisplay the CSS display enum for aTargetContent if
+ * already known, UNSET_DISPLAY otherwise. It will be filled in
+ * if needed.
+ * @param aParentFrame the nearest ancestor frame, used inernally for
+ * finding ::after / ::before frames
+ */
+ nsIFrame* FindNextSibling(mozilla::dom::FlattenedChildIterator aIter,
+ nsIContent* aTargetContent,
+ mozilla::StyleDisplay& aTargetContentDisplay,
+ nsContainerFrame* aParentFrame);
+
+ // Find the right previous sibling for an insertion. This also updates the
+ // parent frame to point to the correct continuation of the parent frame to
+ // use, and returns whether this insertion is to be treated as an append.
+ // aChild is the child being inserted.
+ // aIsRangeInsertSafe returns whether it is safe to do a range insert with
+ // aChild being the first child in the range. It is the callers'
+ // responsibility to check whether a range insert is safe with regards to
+ // fieldsets.
+ // The skip parameters are used to ignore a range of children when looking
+ // for a sibling. All nodes starting from aStartSkipChild and up to but not
+ // including aEndSkipChild will be skipped over when looking for sibling
+ // frames. Skipping a range can deal with XBL but not when there are multiple
+ // insertion points.
+ nsIFrame* GetInsertionPrevSibling(InsertionPoint* aInsertion, // inout
+ nsIContent* aChild,
+ bool* aIsAppend,
+ bool* aIsRangeInsertSafe,
+ nsIContent* aStartSkipChild = nullptr,
+ nsIContent *aEndSkipChild = nullptr);
+
+ /**
+ * Return the insertion frame of the primary frame of aContent, or its nearest
+ * ancestor that isn't display:contents.
+ */
+ nsContainerFrame* GetContentInsertionFrameFor(nsIContent* aContent);
+
+ // see if aContent and aSibling are legitimate siblings due to restrictions
+ // imposed by table columns
+ // XXXbz this code is generally wrong, since the frame for aContent
+ // may be constructed based on tag, not based on aDisplay!
+ bool IsValidSibling(nsIFrame* aSibling,
+ nsIContent* aContent,
+ mozilla::StyleDisplay& aDisplay);
+
+ void QuotesDirty() {
+ NS_PRECONDITION(mUpdateCount != 0, "Instant quote updates are bad news");
+ mQuotesDirty = true;
+ mDocument->SetNeedLayoutFlush();
+ }
+
+ void CountersDirty() {
+ NS_PRECONDITION(mUpdateCount != 0, "Instant counter updates are bad news");
+ mCountersDirty = true;
+ mDocument->SetNeedLayoutFlush();
+ }
+
+ /**
+ * Add the pair (aContent, aStyleContext) to the undisplayed items
+ * in aList as needed. This method enforces the invariant that all
+ * style contexts in the undisplayed content map must be non-pseudo
+ * contexts and also handles unbinding undisplayed generated content
+ * as needed.
+ */
+ void SetAsUndisplayedContent(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aList,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ bool aIsGeneratedContent);
+ // Create touch caret frame.
+ void ConstructAnonymousContentForCanvas(nsFrameConstructorState& aState,
+ nsIFrame* aFrame,
+ nsIContent* aDocElement);
+
+public:
+
+ friend class nsFrameConstructorState;
+
+private:
+
+ nsIDocument* mDocument; // Weak ref
+
+ // See the comment at the start of ConstructRootFrame for more details
+ // about the following frames.
+
+ // This is just the outermost frame for the root element.
+ nsContainerFrame* mRootElementFrame;
+ // This is the frame for the root element that has no pseudo-element style.
+ nsIFrame* mRootElementStyleFrame;
+ // This is the containing block that contains the root element ---
+ // the real "initial containing block" according to CSS 2.1.
+ nsContainerFrame* mDocElementContainingBlock;
+ nsIFrame* mGfxScrollFrame;
+ nsIFrame* mPageSequenceFrame;
+ nsQuoteList mQuoteList;
+ nsCounterManager mCounterManager;
+ // Current ProcessChildren depth.
+ uint16_t mCurrentDepth;
+#ifdef DEBUG
+ uint16_t mUpdateCount;
+#endif
+ bool mQuotesDirty : 1;
+ bool mCountersDirty : 1;
+ bool mIsDestroyingFrameTree : 1;
+ // This is true if mDocElementContainingBlock supports absolute positioning
+ bool mHasRootAbsPosContainingBlock : 1;
+ bool mAlwaysCreateFramesForIgnorableWhitespace : 1;
+
+ nsCOMPtr<nsILayoutHistoryState> mTempFrameTreeState;
+};
+
+#endif /* nsCSSFrameConstructor_h___ */
diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp
new file mode 100644
index 000000000..1c6d97395
--- /dev/null
+++ b/layout/base/nsCSSRendering.cpp
@@ -0,0 +1,6138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#include <ctime>
+
+#include "gfx2DGlue.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "BorderConsts.h"
+#include "nsISVGChildFrame.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsIPresShell.h"
+#include "nsFrameManager.h"
+#include "nsStyleContext.h"
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsIContent.h"
+#include "nsIDocumentInlines.h"
+#include "nsIScrollableFrame.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "ImageOps.h"
+#include "nsCSSRendering.h"
+#include "nsCSSColorUtils.h"
+#include "nsITheme.h"
+#include "nsThemeConstants.h"
+#include "nsLayoutUtils.h"
+#include "nsBlockFrame.h"
+#include "gfxContext.h"
+#include "nsRenderingContext.h"
+#include "nsStyleStructInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSProps.h"
+#include "nsContentUtils.h"
+#include "nsSVGEffects.h"
+#include "nsSVGIntegrationUtils.h"
+#include "gfxDrawable.h"
+#include "GeckoProfiler.h"
+#include "nsCSSRenderingBorders.h"
+#include "mozilla/css/ImageLoader.h"
+#include "ImageContainer.h"
+#include "mozilla/Telemetry.h"
+#include "gfxUtils.h"
+#include "gfxGradientCache.h"
+#include "nsInlineFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using mozilla::CSSSizeOrRatio;
+
+static int gFrameTreeLockCount = 0;
+
+// To avoid storing this data on nsInlineFrame (bloat) and to avoid
+// recalculating this for each frame in a continuation (perf), hold
+// a cache of various coordinate information that we need in order
+// to paint inline backgrounds.
+struct InlineBackgroundData
+{
+ InlineBackgroundData()
+ : mFrame(nullptr), mLineContainer(nullptr)
+ {
+ }
+
+ ~InlineBackgroundData()
+ {
+ }
+
+ void Reset()
+ {
+ mBoundingBox.SetRect(0,0,0,0);
+ mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
+ mFrame = mLineContainer = nullptr;
+ mPIStartBorderData.Reset();
+ }
+
+ /**
+ * Return a continuous rect for (an inline) aFrame relative to the
+ * continuation that draws the left-most part of the background.
+ * This is used when painting backgrounds.
+ */
+ nsRect GetContinuousRect(nsIFrame* aFrame)
+ {
+ MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
+
+ SetFrame(aFrame);
+
+ nscoord pos; // an x coordinate if writing-mode is horizontal;
+ // y coordinate if vertical
+ if (mBidiEnabled) {
+ pos = mLineContinuationPoint;
+
+ // Scan continuations on the same line as aFrame and accumulate the widths
+ // of frames that are to the left (if this is an LTR block) or right
+ // (if it's RTL) of the current one.
+ bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
+ NS_STYLE_DIRECTION_RTL);
+ nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
+ : aFrame->GetOffsetTo(mLineContainer).x;
+
+ // If the continuation is fluid we know inlineFrame is not on the same line.
+ // If it's not fluid, we need to test further to be sure.
+ nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
+ while (inlineFrame && !inlineFrame->GetNextInFlow() &&
+ AreOnSameLine(aFrame, inlineFrame)) {
+ nscoord frameOffset = mVertical
+ ? inlineFrame->GetOffsetTo(mLineContainer).y
+ : inlineFrame->GetOffsetTo(mLineContainer).x;
+ if (isRtlBlock == (frameOffset >= curOffset)) {
+ pos += mVertical
+ ? inlineFrame->GetSize().height
+ : inlineFrame->GetSize().width;
+ }
+ inlineFrame = inlineFrame->GetPrevContinuation();
+ }
+
+ inlineFrame = aFrame->GetNextContinuation();
+ while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
+ AreOnSameLine(aFrame, inlineFrame)) {
+ nscoord frameOffset = mVertical
+ ? inlineFrame->GetOffsetTo(mLineContainer).y
+ : inlineFrame->GetOffsetTo(mLineContainer).x;
+ if (isRtlBlock == (frameOffset >= curOffset)) {
+ pos += mVertical
+ ? inlineFrame->GetSize().height
+ : inlineFrame->GetSize().width;
+ }
+ inlineFrame = inlineFrame->GetNextContinuation();
+ }
+ if (isRtlBlock) {
+ // aFrame itself is also to the right of its left edge, so add its width.
+ pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
+ // pos is now the distance from the left [top] edge of aFrame to the right [bottom] edge
+ // of the unbroken content. Change it to indicate the distance from the
+ // left [top] edge of the unbroken content to the left [top] edge of aFrame.
+ pos = mUnbrokenMeasure - pos;
+ }
+ } else {
+ pos = mContinuationPoint;
+ }
+
+ // Assume background-origin: border and return a rect with offsets
+ // relative to (0,0). If we have a different background-origin,
+ // then our rect should be deflated appropriately by our caller.
+ return mVertical
+ ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
+ : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
+ }
+
+ /**
+ * Return a continuous rect for (an inline) aFrame relative to the
+ * continuation that should draw the left[top]-border. This is used when painting
+ * borders and clipping backgrounds. This may NOT be the same continuous rect
+ * as for drawing backgrounds; the continuation with the left[top]-border might be
+ * somewhere in the middle of that rect (e.g. BIDI), in those cases we need
+ * the reverse background order starting at the left[top]-border continuation.
+ */
+ nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea)
+ {
+ // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
+ // resets our mPIStartBorderData so we save it ...
+ PhysicalInlineStartBorderData saved(mPIStartBorderData);
+ nsRect joinedBorderArea = GetContinuousRect(aFrame);
+ if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
+ if (aFrame == mPIStartBorderData.mFrame) {
+ if (mVertical) {
+ mPIStartBorderData.SetCoord(joinedBorderArea.y);
+ } else {
+ mPIStartBorderData.SetCoord(joinedBorderArea.x);
+ }
+ } else if (mPIStartBorderData.mFrame) {
+ if (mVertical) {
+ mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).y);
+ } else {
+ mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).x);
+ }
+ }
+ } else {
+ // ... and restore it when possible.
+ mPIStartBorderData.mCoord = saved.mCoord;
+ }
+ if (mVertical) {
+ if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
+ joinedBorderArea.y =
+ -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
+ } else {
+ joinedBorderArea.y -= mPIStartBorderData.mCoord;
+ }
+ } else {
+ if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
+ joinedBorderArea.x =
+ -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
+ } else {
+ joinedBorderArea.x -= mPIStartBorderData.mCoord;
+ }
+ }
+ return joinedBorderArea;
+ }
+
+ nsRect GetBoundingRect(nsIFrame* aFrame)
+ {
+ SetFrame(aFrame);
+
+ // Move the offsets relative to (0,0) which puts the bounding box into
+ // our coordinate system rather than our parent's. We do this by
+ // moving it the back distance from us to the bounding box.
+ // This also assumes background-origin: border, so our caller will
+ // need to deflate us if needed.
+ nsRect boundingBox(mBoundingBox);
+ nsPoint point = mFrame->GetPosition();
+ boundingBox.MoveBy(-point.x, -point.y);
+
+ return boundingBox;
+ }
+
+protected:
+ // This is a coordinate on the inline axis, but is not a true logical inline-
+ // coord because it is always measured from left to right (if horizontal) or
+ // from top to bottom (if vertical), ignoring any bidi RTL directionality.
+ // We'll call this "physical inline start", or PIStart for short.
+ struct PhysicalInlineStartBorderData {
+ nsIFrame* mFrame; // the continuation that may have a left-border
+ nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
+ bool mIsValid; // true if mCoord is valid
+ void Reset() { mFrame = nullptr; mIsValid = false; }
+ void SetCoord(nscoord aCoord) { mCoord = aCoord; mIsValid = true; }
+ };
+
+ nsIFrame* mFrame;
+ nsIFrame* mLineContainer;
+ nsRect mBoundingBox;
+ nscoord mContinuationPoint;
+ nscoord mUnbrokenMeasure;
+ nscoord mLineContinuationPoint;
+ PhysicalInlineStartBorderData mPIStartBorderData;
+ bool mBidiEnabled;
+ bool mVertical;
+
+ void SetFrame(nsIFrame* aFrame)
+ {
+ NS_PRECONDITION(aFrame, "Need a frame");
+ NS_ASSERTION(gFrameTreeLockCount > 0,
+ "Can't call this when frame tree is not locked");
+
+ if (aFrame == mFrame) {
+ return;
+ }
+
+ nsIFrame *prevContinuation = GetPrevContinuation(aFrame);
+
+ if (!prevContinuation || mFrame != prevContinuation) {
+ // Ok, we've got the wrong frame. We have to start from scratch.
+ Reset();
+ Init(aFrame);
+ return;
+ }
+
+ // Get our last frame's size and add its width to our continuation
+ // point before we cache the new frame.
+ mContinuationPoint += mVertical ? mFrame->GetSize().height
+ : mFrame->GetSize().width;
+
+ // If this a new line, update mLineContinuationPoint.
+ if (mBidiEnabled &&
+ (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
+ mLineContinuationPoint = mContinuationPoint;
+ }
+
+ mFrame = aFrame;
+ }
+
+ nsIFrame* GetPrevContinuation(nsIFrame* aFrame)
+ {
+ nsIFrame* prevCont = aFrame->GetPrevContinuation();
+ if (!prevCont &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ nsIFrame* block =
+ aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ if (block) {
+ // The {ib} properties are only stored on first continuations
+ NS_ASSERTION(!block->GetPrevContinuation(),
+ "Incorrect value for IBSplitPrevSibling");
+ prevCont =
+ block->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ NS_ASSERTION(prevCont, "How did that happen?");
+ }
+ }
+ return prevCont;
+ }
+
+ nsIFrame* GetNextContinuation(nsIFrame* aFrame)
+ {
+ nsIFrame* nextCont = aFrame->GetNextContinuation();
+ if (!nextCont &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // The {ib} properties are only stored on first continuations
+ aFrame = aFrame->FirstContinuation();
+ nsIFrame* block = aFrame->Properties().Get(nsIFrame::IBSplitSibling());
+ if (block) {
+ nextCont = block->Properties().Get(nsIFrame::IBSplitSibling());
+ NS_ASSERTION(nextCont, "How did that happen?");
+ }
+ }
+ return nextCont;
+ }
+
+ void Init(nsIFrame* aFrame)
+ {
+ mPIStartBorderData.Reset();
+ mBidiEnabled = aFrame->PresContext()->BidiEnabled();
+ if (mBidiEnabled) {
+ // Find the line container frame
+ mLineContainer = aFrame;
+ while (mLineContainer &&
+ mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ mLineContainer = mLineContainer->GetParent();
+ }
+
+ MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
+ MOZ_ASSERT(mLineContainer != aFrame, "line container frame "
+ "should be an ancestor of the target frame.");
+ }
+
+ mVertical = aFrame->GetWritingMode().IsVertical();
+
+ // Start with the previous flow frame as our continuation point
+ // is the total of the widths of the previous frames.
+ nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
+ while (inlineFrame) {
+ if (!mPIStartBorderData.mFrame &&
+ !(mVertical ? inlineFrame->GetSkipSides().Top()
+ : inlineFrame->GetSkipSides().Left())) {
+ mPIStartBorderData.mFrame = inlineFrame;
+ }
+ nsRect rect = inlineFrame->GetRect();
+ mContinuationPoint += mVertical ? rect.height : rect.width;
+ if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) {
+ mLineContinuationPoint += mVertical ? rect.height : rect.width;
+ }
+ mUnbrokenMeasure += mVertical ? rect.height : rect.width;
+ mBoundingBox.UnionRect(mBoundingBox, rect);
+ inlineFrame = GetPrevContinuation(inlineFrame);
+ }
+
+ // Next add this frame and subsequent frames to the bounding box and
+ // unbroken width.
+ inlineFrame = aFrame;
+ while (inlineFrame) {
+ if (!mPIStartBorderData.mFrame &&
+ !(mVertical ? inlineFrame->GetSkipSides().Top()
+ : inlineFrame->GetSkipSides().Left())) {
+ mPIStartBorderData.mFrame = inlineFrame;
+ }
+ nsRect rect = inlineFrame->GetRect();
+ mUnbrokenMeasure += mVertical ? rect.height : rect.width;
+ mBoundingBox.UnionRect(mBoundingBox, rect);
+ inlineFrame = GetNextContinuation(inlineFrame);
+ }
+
+ mFrame = aFrame;
+ }
+
+ bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
+ if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
+ bool isValid1, isValid2;
+ nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
+ nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
+ return isValid1 && isValid2 &&
+ // Make sure aFrame1 and aFrame2 are in the same continuation of
+ // blockFrame.
+ it1.GetContainer() == it2.GetContainer() &&
+ // And on the same line in it
+ it1.GetLine() == it2.GetLine();
+ }
+ if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
+ nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
+ // Ruby text container can only hold one line of text, so if they
+ // are in the same continuation, they are in the same line. Since
+ // ruby text containers are bidi isolate, they are never split for
+ // bidi reordering, which means being in different continuation
+ // indicates being in different lines.
+ for (nsIFrame* frame = rtcFrame->FirstContinuation();
+ frame; frame = frame->GetNextContinuation()) {
+ bool isDescendant1 =
+ nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
+ bool isDescendant2 =
+ nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
+ if (isDescendant1 && isDescendant2) {
+ return true;
+ }
+ if (isDescendant1 || isDescendant2) {
+ return false;
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
+ }
+ MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
+ return false;
+ }
+};
+
+// A resolved color stop, with a specific position along the gradient line and
+// a color.
+struct ColorStop {
+ ColorStop(): mPosition(0), mIsMidpoint(false) {}
+ ColorStop(double aPosition, bool aIsMidPoint, const Color& aColor) :
+ mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
+ double mPosition; // along the gradient line; 0=start, 1=end
+ bool mIsMidpoint;
+ Color mColor;
+};
+
+/* Local functions */
+static DrawResult DrawBorderImage(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder,
+ const nsRect& aDirtyRect,
+ Sides aSkipSides,
+ PaintBorderFlags aFlags);
+
+static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
+ nscolor aBackgroundColor,
+ nscolor aBorderColor);
+
+static InlineBackgroundData* gInlineBGData = nullptr;
+
+// Initialize any static variables used by nsCSSRendering.
+void nsCSSRendering::Init()
+{
+ NS_ASSERTION(!gInlineBGData, "Init called twice");
+ gInlineBGData = new InlineBackgroundData();
+}
+
+// Clean up any global variables used by nsCSSRendering.
+void nsCSSRendering::Shutdown()
+{
+ delete gInlineBGData;
+ gInlineBGData = nullptr;
+}
+
+/**
+ * Make a bevel color
+ */
+static nscolor
+MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
+ nscolor aBackgroundColor, nscolor aBorderColor)
+{
+
+ nscolor colors[2];
+ nscolor theColor;
+
+ // Given a background color and a border color
+ // calculate the color used for the shading
+ NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
+
+ if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
+ (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
+ // Flip colors for these two border styles
+ switch (whichSide) {
+ case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP; break;
+ case NS_SIDE_RIGHT: whichSide = NS_SIDE_LEFT; break;
+ case NS_SIDE_TOP: whichSide = NS_SIDE_BOTTOM; break;
+ case NS_SIDE_LEFT: whichSide = NS_SIDE_RIGHT; break;
+ }
+ }
+
+ switch (whichSide) {
+ case NS_SIDE_BOTTOM:
+ theColor = colors[1];
+ break;
+ case NS_SIDE_RIGHT:
+ theColor = colors[1];
+ break;
+ case NS_SIDE_TOP:
+ theColor = colors[0];
+ break;
+ case NS_SIDE_LEFT:
+ default:
+ theColor = colors[0];
+ break;
+ }
+ return theColor;
+}
+
+static bool
+GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
+ nscoord aRadii[8])
+{
+ bool haveRoundedCorners;
+ nsSize sz = aBorderArea.Size();
+ nsSize frameSize = aForFrame->GetSize();
+ if (&aBorder == aForFrame->StyleBorder() &&
+ frameSize == aOrigBorderArea.Size()) {
+ haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
+ } else {
+ haveRoundedCorners =
+ nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
+ }
+
+ return haveRoundedCorners;
+}
+
+static bool
+GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
+ RectCornerRadii* aBgRadii)
+{
+ nscoord radii[8];
+ bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
+
+ if (haveRoundedCorners) {
+ auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
+ }
+ return haveRoundedCorners;
+}
+
+static nsRect
+JoinBoxesForVerticalSlice(nsIFrame* aFrame, const nsRect& aBorderArea)
+{
+ // Inflate vertically as if our continuations were laid out vertically
+ // adjacent. Note that we don't touch the width.
+ nsRect borderArea = aBorderArea;
+ nscoord h = 0;
+ nsIFrame* f = aFrame->GetNextContinuation();
+ for (; f; f = f->GetNextContinuation()) {
+ MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
+ "anonymous ib-split block shouldn't have border/background");
+ h += f->GetRect().height;
+ }
+ borderArea.height += h;
+ h = 0;
+ f = aFrame->GetPrevContinuation();
+ for (; f; f = f->GetPrevContinuation()) {
+ MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
+ "anonymous ib-split block shouldn't have border/background");
+ h += f->GetRect().height;
+ }
+ borderArea.y -= h;
+ borderArea.height += h;
+ return borderArea;
+}
+
+/**
+ * Inflate aBorderArea which is relative to aFrame's origin to calculate
+ * a hypothetical non-split frame area for all the continuations.
+ * See "Joining Boxes for 'slice'" in
+ * http://dev.w3.org/csswg/css-break/#break-decoration
+ */
+enum InlineBoxOrder { eForBorder, eForBackground };
+static nsRect
+JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
+ InlineBoxOrder aOrder)
+{
+ if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
+ return (aOrder == eForBorder
+ ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
+ : gInlineBGData->GetContinuousRect(aFrame)) +
+ aBorderArea.TopLeft();
+ }
+ return JoinBoxesForVerticalSlice(aFrame, aBorderArea);
+}
+
+static bool
+IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder)
+{
+ return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
+}
+
+static nsRect
+BoxDecorationRectForBorder(nsIFrame* aFrame, const nsRect& aBorderArea,
+ Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder = nullptr)
+{
+ if (!aStyleBorder) {
+ aStyleBorder = aFrame->StyleBorder();
+ }
+ // If aSkipSides.IsEmpty() then there are no continuations, or it's
+ // a ::first-letter that wants all border sides on the first continuation.
+ return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
+ ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
+ : aBorderArea;
+}
+
+static nsRect
+BoxDecorationRectForBackground(nsIFrame* aFrame, const nsRect& aBorderArea,
+ Sides aSkipSides,
+ const nsStyleBorder* aStyleBorder = nullptr)
+{
+ if (!aStyleBorder) {
+ aStyleBorder = aFrame->StyleBorder();
+ }
+ // If aSkipSides.IsEmpty() then there are no continuations, or it's
+ // a ::first-letter that wants all border sides on the first continuation.
+ return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
+ ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
+ : aBorderArea;
+}
+
+//----------------------------------------------------------------------
+// Thebes Border Rendering Code Start
+
+/*
+ * Compute the float-pixel radii that should be used for drawing
+ * this border/outline, given the various input bits.
+ */
+/* static */ void
+nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
+ nscoord aAppUnitsPerPixel,
+ RectCornerRadii *oBorderRadii)
+{
+ Float radii[8];
+ NS_FOR_CSS_HALF_CORNERS(corner)
+ radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
+
+ (*oBorderRadii)[C_TL] = Size(radii[NS_CORNER_TOP_LEFT_X],
+ radii[NS_CORNER_TOP_LEFT_Y]);
+ (*oBorderRadii)[C_TR] = Size(radii[NS_CORNER_TOP_RIGHT_X],
+ radii[NS_CORNER_TOP_RIGHT_Y]);
+ (*oBorderRadii)[C_BR] = Size(radii[NS_CORNER_BOTTOM_RIGHT_X],
+ radii[NS_CORNER_BOTTOM_RIGHT_Y]);
+ (*oBorderRadii)[C_BL] = Size(radii[NS_CORNER_BOTTOM_LEFT_X],
+ radii[NS_CORNER_BOTTOM_LEFT_Y]);
+}
+
+DrawResult
+nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsStyleContext* aStyleContext,
+ PaintBorderFlags aFlags,
+ Sides aSkipSides)
+{
+ PROFILER_LABEL("nsCSSRendering", "PaintBorder",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
+ const nsStyleBorder *styleBorder = aStyleContext->StyleBorder();
+ // Don't check RelevantLinkVisited here, since we want to take the
+ // same amount of time whether or not it's true.
+ if (!styleIfVisited) {
+ return PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
+ aDirtyRect, aBorderArea, *styleBorder,
+ aStyleContext, aFlags, aSkipSides);
+ }
+
+ nsStyleBorder newStyleBorder(*styleBorder);
+
+ NS_FOR_CSS_SIDES(side) {
+ nscolor color = aStyleContext->GetVisitedDependentColor(
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side]);
+ newStyleBorder.mBorderColor[side] = StyleComplexColor::FromColor(color);
+ }
+ DrawResult result =
+ PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
+ aDirtyRect, aBorderArea, newStyleBorder,
+ aStyleContext, aFlags, aSkipSides);
+
+ return result;
+}
+
+DrawResult
+nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder,
+ nsStyleContext* aStyleContext,
+ PaintBorderFlags aFlags,
+ Sides aSkipSides)
+{
+ DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
+
+ PrintAsStringNewline("++ PaintBorder");
+
+ // Check to see if we have an appearance defined. If so, we let the theme
+ // renderer draw the border. DO not get the data from aForFrame, since the passed in style context
+ // may be different! Always use |aStyleContext|!
+ const nsStyleDisplay* displayData = aStyleContext->StyleDisplay();
+ if (displayData->mAppearance) {
+ nsITheme *theme = aPresContext->GetTheme();
+ if (theme &&
+ theme->ThemeSupportsWidget(aPresContext, aForFrame,
+ displayData->mAppearance)) {
+ return DrawResult::SUCCESS; // Let the theme handle it.
+ }
+ }
+
+ if (aStyleBorder.IsBorderImageLoaded()) {
+ return DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
+ aBorderArea, aStyleBorder, aDirtyRect,
+ aSkipSides, aFlags);
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // If we had a border-image, but it wasn't loaded, then we should return
+ // DrawResult::NOT_READY; we'll want to try again if we do a paint with sync
+ // decoding enabled.
+ if (aStyleBorder.mBorderImageSource.GetType() != eStyleImageType_Null) {
+ result = DrawResult::NOT_READY;
+ }
+
+ // Get our style context's color struct.
+ const nsStyleColor* ourColor = aStyleContext->StyleColor();
+
+ // In NavQuirks mode we want to use the parent's context as a starting point
+ // for determining the background color.
+ bool quirks = aPresContext->CompatibilityMode() == eCompatibility_NavQuirks;
+ nsIFrame* bgFrame = FindNonTransparentBackgroundFrame(aForFrame, quirks);
+ nsStyleContext* bgContext = bgFrame->StyleContext();
+ nscolor bgColor =
+ bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
+
+ nsMargin border = aStyleBorder.GetComputedBorder();
+ if (0 == border.left && 0 == border.right &&
+ 0 == border.top && 0 == border.bottom) {
+ // Empty border area
+ return result;
+ }
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aBorderArea & aBGClipRect.
+ nsRect joinedBorderArea =
+ ::BoxDecorationRectForBorder(aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
+ RectCornerRadii bgRadii;
+ ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
+
+ PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, joinedBorderArea.y,
+ joinedBorderArea.width, joinedBorderArea.height);
+
+ // start drawing
+ bool needToPopClip = false;
+
+ if (::IsBoxDecorationSlice(aStyleBorder)) {
+ if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
+ // No need for a clip, just skip the sides we don't want.
+ border.ApplySkipSides(aSkipSides);
+ } else {
+ // We're drawing borders around the joined continuation boxes so we need
+ // to clip that to the slice that we want for this frame.
+ aDrawTarget.PushClipRect(
+ NSRectToSnappedRect(aBorderArea,
+ aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget));
+ needToPopClip = true;
+ }
+ } else {
+ MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
+ "Should use aBorderArea for box-decoration-break:clone");
+ MOZ_ASSERT(aForFrame->GetSkipSides().IsEmpty() ||
+ IS_TRUE_OVERFLOW_CONTAINER(aForFrame),
+ "Should not skip sides for box-decoration-break:clone except "
+ "::first-letter/line continuations or other frame types that "
+ "don't have borders but those shouldn't reach this point. "
+ "Overflow containers do reach this point though.");
+ border.ApplySkipSides(aSkipSides);
+ }
+
+ // Convert to dev pixels.
+ nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
+ Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, twipsPerPixel);
+ Float borderWidths[4] = { Float(border.top / twipsPerPixel),
+ Float(border.right / twipsPerPixel),
+ Float(border.bottom / twipsPerPixel),
+ Float(border.left / twipsPerPixel) };
+ Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
+
+ uint8_t borderStyles[4];
+ nscolor borderColors[4];
+ nsBorderColors *compositeColors[4];
+
+ // pull out styles, colors, composite colors
+ NS_FOR_CSS_SIDES (i) {
+ borderStyles[i] = aStyleBorder.GetBorderStyle(i);
+ borderColors[i] = ourColor->CalcComplexColor(aStyleBorder.mBorderColor[i]);
+ aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
+ }
+
+ PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
+ //PrintAsFormatString ("bgRadii: %f %f %f %f\n", bgRadii[0], bgRadii[1], bgRadii[2], bgRadii[3]);
+
+#if 0
+ // this will draw a transparent red backround underneath the border area
+ ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 0.5f)));
+ aDrawTarget.FillRect(joinedBorderAreaPx, color);
+#endif
+
+ nsIDocument* document = nullptr;
+ nsIContent* content = aForFrame->GetContent();
+ if (content) {
+ document = content->OwnerDoc();
+ }
+
+ nsCSSBorderRenderer br(aPresContext,
+ document,
+ &aDrawTarget,
+ dirtyRect,
+ joinedBorderAreaPx,
+ borderStyles,
+ borderWidths,
+ bgRadii,
+ borderColors,
+ compositeColors,
+ bgColor);
+ br.DrawBorders();
+
+ if (needToPopClip) {
+ aDrawTarget.PopClip();
+ }
+
+ PrintAsStringNewline();
+
+ return result;
+}
+
+static nsRect
+GetOutlineInnerRect(nsIFrame* aFrame)
+{
+ nsRect* savedOutlineInnerRect =
+ aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty());
+ if (savedOutlineInnerRect)
+ return *savedOutlineInnerRect;
+ NS_NOTREACHED("we should have saved a frame property");
+ return nsRect(nsPoint(0, 0), aFrame->GetSize());
+}
+
+void
+nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsStyleContext* aStyleContext)
+{
+ nscoord twipsRadii[8];
+
+ // Get our style context's color struct.
+ const nsStyleOutline* ourOutline = aStyleContext->StyleOutline();
+ MOZ_ASSERT(ourOutline != NS_STYLE_BORDER_STYLE_NONE,
+ "shouldn't have created nsDisplayOutline item");
+
+ uint8_t outlineStyle = ourOutline->mOutlineStyle;
+ nscoord width = ourOutline->GetOutlineWidth();
+
+ if (width == 0 && outlineStyle != NS_STYLE_BORDER_STYLE_AUTO) {
+ // Empty outline
+ return;
+ }
+
+ nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
+ (aForFrame, false);
+ nsStyleContext* bgContext = bgFrame->StyleContext();
+ nscolor bgColor =
+ bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
+
+ nsRect innerRect;
+ if (
+#ifdef MOZ_XUL
+ aStyleContext->GetPseudoType() == CSSPseudoElementType::XULTree
+#else
+ false
+#endif
+ ) {
+ innerRect = aBorderArea;
+ } else {
+ innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
+ }
+ nscoord offset = ourOutline->mOutlineOffset;
+ innerRect.Inflate(offset, offset);
+ // If the dirty rect is completely inside the border area (e.g., only the
+ // content is being painted), then we can skip out now
+ // XXX this isn't exactly true for rounded borders, where the inside curves may
+ // encroach into the content area. A safer calculation would be to
+ // shorten insideRect by the radius one each side before performing this test.
+ if (innerRect.Contains(aDirtyRect))
+ return;
+
+ nsRect outerRect = innerRect;
+ outerRect.Inflate(width, width);
+
+ // get the radius for our outline
+ nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
+ outerRect.Size(), Sides(), twipsRadii);
+
+ // Get our conversion values
+ nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ // get the outer rectangles
+ Rect oRect(NSRectToRect(outerRect, twipsPerPixel));
+
+ // convert the radii
+ nsMargin outlineMargin(width, width, width, width);
+ RectCornerRadii outlineRadii;
+ ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
+
+ if (outlineStyle == NS_STYLE_BORDER_STYLE_AUTO) {
+ if (nsLayoutUtils::IsOutlineStyleAutoEnabled()) {
+ nsITheme* theme = aPresContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
+ NS_THEME_FOCUS_OUTLINE)) {
+ theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
+ NS_THEME_FOCUS_OUTLINE, innerRect,
+ aDirtyRect);
+ return;
+ }
+ }
+ if (width == 0) {
+ return; // empty outline
+ }
+ // http://dev.w3.org/csswg/css-ui/#outline
+ // "User agents may treat 'auto' as 'solid'."
+ outlineStyle = NS_STYLE_BORDER_STYLE_SOLID;
+ }
+
+ uint8_t outlineStyles[4] = { outlineStyle, outlineStyle,
+ outlineStyle, outlineStyle };
+
+ // This handles treating the initial color as 'currentColor'; if we
+ // ever want 'invert' back we'll need to do a bit of work here too.
+ nscolor outlineColor =
+ aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color);
+ nscolor outlineColors[4] = { outlineColor,
+ outlineColor,
+ outlineColor,
+ outlineColor };
+
+ // convert the border widths
+ Float outlineWidths[4] = { Float(width / twipsPerPixel),
+ Float(width / twipsPerPixel),
+ Float(width / twipsPerPixel),
+ Float(width / twipsPerPixel) };
+ Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
+
+ nsIDocument* document = nullptr;
+ nsIContent* content = aForFrame->GetContent();
+ if (content) {
+ document = content->OwnerDoc();
+ }
+
+ // start drawing
+
+ nsCSSBorderRenderer br(aPresContext,
+ document,
+ aRenderingContext.GetDrawTarget(),
+ dirtyRect,
+ oRect,
+ outlineStyles,
+ outlineWidths,
+ outlineRadii,
+ outlineColors,
+ nullptr,
+ bgColor);
+ br.DrawBorders();
+
+ PrintAsStringNewline();
+}
+
+void
+nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ const nsRect& aFocusRect,
+ nscolor aColor)
+{
+ nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
+ nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
+
+ RectCornerRadii focusRadii;
+ {
+ nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
+ }
+ Float focusWidths[4] = { Float(oneCSSPixel / oneDevPixel),
+ Float(oneCSSPixel / oneDevPixel),
+ Float(oneCSSPixel / oneDevPixel),
+ Float(oneCSSPixel / oneDevPixel) };
+
+ uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
+ NS_STYLE_BORDER_STYLE_DOTTED,
+ NS_STYLE_BORDER_STYLE_DOTTED,
+ NS_STYLE_BORDER_STYLE_DOTTED };
+ nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
+
+ // Because this renders a dotted border, the background color
+ // should not be used. Therefore, we provide a value that will
+ // be blatantly wrong if it ever does get used. (If this becomes
+ // something that CSS can style, this function will then have access
+ // to a style context and can use the same logic that PaintBorder
+ // and PaintOutline do.)
+ nsCSSBorderRenderer br(aPresContext,
+ nullptr,
+ aDrawTarget,
+ focusRect,
+ focusRect,
+ focusStyles,
+ focusWidths,
+ focusRadii,
+ focusColors,
+ nullptr,
+ NS_RGB(255, 0, 0));
+ br.DrawBorders();
+
+ PrintAsStringNewline();
+}
+
+// Thebes Border Rendering Code End
+//----------------------------------------------------------------------
+
+
+//----------------------------------------------------------------------
+
+/**
+ * Helper for ComputeObjectAnchorPoint; parameters are the same as for
+ * that function, except they're for a single coordinate / a single size
+ * dimension. (so, x/width vs. y/height)
+ */
+static void
+ComputeObjectAnchorCoord(const Position::Coord& aCoord,
+ const nscoord aOriginBounds,
+ const nscoord aImageSize,
+ nscoord* aTopLeftCoord,
+ nscoord* aAnchorPointCoord)
+{
+ *aAnchorPointCoord = aCoord.mLength;
+ *aTopLeftCoord = aCoord.mLength;
+
+ if (aCoord.mHasPercent) {
+ // Adjust aTopLeftCoord by the specified % of the extra space.
+ nscoord extraSpace = aOriginBounds - aImageSize;
+ *aTopLeftCoord += NSToCoordRound(aCoord.mPercent * extraSpace);
+
+ // The anchor-point doesn't care about our image's size; just the size
+ // of the region we're rendering into.
+ *aAnchorPointCoord += NSToCoordRound(aCoord.mPercent * aOriginBounds);
+ }
+}
+
+void
+nsImageRenderer::ComputeObjectAnchorPoint(
+ const Position& aPos,
+ const nsSize& aOriginBounds,
+ const nsSize& aImageSize,
+ nsPoint* aTopLeft,
+ nsPoint* aAnchorPoint)
+{
+ ComputeObjectAnchorCoord(aPos.mXPosition,
+ aOriginBounds.width, aImageSize.width,
+ &aTopLeft->x, &aAnchorPoint->x);
+
+ ComputeObjectAnchorCoord(aPos.mYPosition,
+ aOriginBounds.height, aImageSize.height,
+ &aTopLeft->y, &aAnchorPoint->y);
+}
+
+nsIFrame*
+nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
+ bool aStartAtParent /*= false*/)
+{
+ NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame");
+
+ nsIFrame* frame = nullptr;
+ if (aStartAtParent) {
+ frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
+ }
+ if (!frame) {
+ frame = aFrame;
+ }
+
+ while (frame) {
+ // No need to call GetVisitedDependentColor because it always uses
+ // this alpha component anyway.
+ if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0)
+ break;
+
+ if (frame->IsThemed())
+ break;
+
+ nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+ if (!parent)
+ break;
+
+ frame = parent;
+ }
+ return frame;
+}
+
+// Returns true if aFrame is a canvas frame.
+// We need to treat the viewport as canvas because, even though
+// it does not actually paint a background, we need to get the right
+// background style so we correctly detect transparent documents.
+bool
+nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame)
+{
+ nsIAtom* frameType = aFrame->GetType();
+ return frameType == nsGkAtoms::canvasFrame ||
+ frameType == nsGkAtoms::rootFrame ||
+ frameType == nsGkAtoms::pageContentFrame ||
+ frameType == nsGkAtoms::viewportFrame;
+}
+
+nsIFrame*
+nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame)
+{
+ const nsStyleBackground* result = aForFrame->StyleBackground();
+
+ // Check if we need to do propagation from BODY rather than HTML.
+ if (!result->IsTransparent()) {
+ return aForFrame;
+ }
+
+ nsIContent* content = aForFrame->GetContent();
+ // The root element content can't be null. We wouldn't know what
+ // frame to create for aFrame.
+ // Use |OwnerDoc| so it works during destruction.
+ if (!content) {
+ return aForFrame;
+ }
+
+ nsIDocument* document = content->OwnerDoc();
+
+ dom::Element* bodyContent = document->GetBodyElement();
+ // We need to null check the body node (bug 118829) since
+ // there are cases, thanks to the fix for bug 5569, where we
+ // will reflow a document with no body. In particular, if a
+ // SCRIPT element in the head blocks the parser and then has a
+ // SCRIPT that does "document.location.href = 'foo'", then
+ // nsParser::Terminate will call |DidBuildModel| methods
+ // through to the content sink, which will call |StartLayout|
+ // and thus |Initialize| on the pres shell. See bug 119351
+ // for the ugly details.
+ if (!bodyContent) {
+ return aForFrame;
+ }
+
+ nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame();
+ if (!bodyFrame) {
+ return aForFrame;
+ }
+
+ return nsLayoutUtils::GetStyleFrame(bodyFrame);
+}
+
+/**
+ * |FindBackground| finds the correct style data to use to paint the
+ * background. It is responsible for handling the following two
+ * statements in section 14.2 of CSS2:
+ *
+ * The background of the box generated by the root element covers the
+ * entire canvas.
+ *
+ * For HTML documents, however, we recommend that authors specify the
+ * background for the BODY element rather than the HTML element. User
+ * agents should observe the following precedence rules to fill in the
+ * background: if the value of the 'background' property for the HTML
+ * element is different from 'transparent' then use it, else use the
+ * value of the 'background' property for the BODY element. If the
+ * resulting value is 'transparent', the rendering is undefined.
+ *
+ * Thus, in our implementation, it is responsible for ensuring that:
+ * + we paint the correct background on the |nsCanvasFrame|,
+ * |nsRootBoxFrame|, or |nsPageFrame|,
+ * + we don't paint the background on the root element, and
+ * + we don't paint the background on the BODY element in *some* cases,
+ * and for SGML-based HTML documents only.
+ *
+ * |FindBackground| returns true if a background should be painted, and
+ * the resulting style context to use for the background information
+ * will be filled in to |aBackground|.
+ */
+nsStyleContext*
+nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame)
+{
+ return FindBackgroundStyleFrame(aForFrame)->StyleContext();
+}
+
+inline bool
+FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame,
+ nsStyleContext** aBackgroundSC)
+{
+ if (aForFrame == aRootElementFrame) {
+ // We must have propagated our background to the viewport or canvas. Abort.
+ return false;
+ }
+
+ *aBackgroundSC = aForFrame->StyleContext();
+
+ // Return true unless the frame is for a BODY element whose background
+ // was propagated to the viewport.
+
+ nsIContent* content = aForFrame->GetContent();
+ if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
+ return true; // not frame for a "body" element
+ // It could be a non-HTML "body" element but that's OK, we'd fail the
+ // bodyContent check below
+
+ if (aForFrame->StyleContext()->GetPseudo())
+ return true; // A pseudo-element frame.
+
+ // We should only look at the <html> background if we're in an HTML document
+ nsIDocument* document = content->OwnerDoc();
+
+ dom::Element* bodyContent = document->GetBodyElement();
+ if (bodyContent != content)
+ return true; // this wasn't the background that was propagated
+
+ // This can be called even when there's no root element yet, during frame
+ // construction, via nsLayoutUtils::FrameHasTransparency and
+ // nsContainerFrame::SyncFrameViewProperties.
+ if (!aRootElementFrame)
+ return true;
+
+ const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
+ return !htmlBG->IsTransparent();
+}
+
+bool
+nsCSSRendering::FindBackground(nsIFrame* aForFrame,
+ nsStyleContext** aBackgroundSC)
+{
+ nsIFrame* rootElementFrame =
+ aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
+ if (IsCanvasFrame(aForFrame)) {
+ *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame);
+ return true;
+ } else {
+ return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC);
+ }
+}
+
+void
+nsCSSRendering::BeginFrameTreesLocked()
+{
+ ++gFrameTreeLockCount;
+}
+
+void
+nsCSSRendering::EndFrameTreesLocked()
+{
+ NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
+ --gFrameTreeLockCount;
+ if (gFrameTreeLockCount == 0) {
+ gInlineBGData->Reset();
+ }
+}
+
+void
+nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea,
+ const nsRect& aDirtyRect,
+ float aOpacity)
+{
+ DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
+ nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
+ if (!shadows)
+ return;
+
+ bool hasBorderRadius;
+ bool nativeTheme; // mutually exclusive with hasBorderRadius
+ const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
+ nsITheme::Transparency transparency;
+ if (aForFrame->IsThemed(styleDisplay, &transparency)) {
+ // We don't respect border-radius for native-themed widgets
+ hasBorderRadius = false;
+ // For opaque (rectangular) theme widgets we can take the generic
+ // border-box path with border-radius disabled.
+ nativeTheme = transparency != nsITheme::eOpaque;
+ } else {
+ nativeTheme = false;
+ hasBorderRadius = true; // we'll update this below
+ }
+
+ nsRect frameRect = nativeTheme ?
+ aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
+ aFrameArea;
+ Sides skipSides = aForFrame->GetSkipSides();
+ frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
+
+ // Get any border radius, since box-shadow must also have rounded corners if
+ // the frame does.
+ RectCornerRadii borderRadii;
+ const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
+ if (hasBorderRadius) {
+ nscoord twipsRadii[8];
+ NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
+ "unexpected size");
+ nsSize sz = frameRect.Size();
+ hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
+ if (hasBorderRadius) {
+ ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
+ }
+ }
+
+
+ // We don't show anything that intersects with the frame we're blurring on. So tell the
+ // blurrer not to do unnecessary work there.
+ gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, twipsPerPixel));
+ skipGfxRect.Round();
+ bool useSkipGfxRect = true;
+ if (nativeTheme) {
+ // Optimize non-leaf native-themed frames by skipping computing pixels
+ // in the padding-box. We assume the padding-box is going to be painted
+ // opaquely for non-leaf frames.
+ // XXX this may not be a safe assumption; we should make this go away
+ // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
+ useSkipGfxRect = !aForFrame->IsLeaf();
+ nsRect paddingRect =
+ aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
+ skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
+ } else if (hasBorderRadius) {
+ skipGfxRect.Deflate(gfxMargin(
+ std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
+ std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
+ }
+
+ gfxContext* renderContext = aRenderingContext.ThebesContext();
+
+ for (uint32_t i = shadows->Length(); i > 0; --i) {
+ nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
+ if (shadowItem->mInset)
+ continue;
+
+ nsRect shadowRect = frameRect;
+ shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
+ if (!nativeTheme) {
+ shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
+ }
+
+ // shadowRect won't include the blur, so make an extra rect here that includes the blur
+ // for use in the even-odd rule below.
+ nsRect shadowRectPlusBlur = shadowRect;
+ nscoord blurRadius = shadowItem->mRadius;
+ shadowRectPlusBlur.Inflate(
+ nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
+
+ Rect shadowGfxRectPlusBlur =
+ NSRectToRect(shadowRectPlusBlur, twipsPerPixel);
+ shadowGfxRectPlusBlur.RoundOut();
+ MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
+
+ // Set the shadow color; if not specified, use the foreground color
+ nscolor shadowColor;
+ if (shadowItem->mHasColor)
+ shadowColor = shadowItem->mColor;
+ else
+ shadowColor = aForFrame->StyleColor()->mColor;
+
+ Color gfxShadowColor(Color::FromABGR(shadowColor));
+ gfxShadowColor.a *= aOpacity;
+
+ if (nativeTheme) {
+ nsContextBoxBlur blurringArea;
+
+ // When getting the widget shape from the native theme, we're going
+ // to draw the widget into the shadow surface to create a mask.
+ // We need to ensure that there actually *is* a shadow surface
+ // and that we're not going to draw directly into renderContext.
+ gfxContext* shadowContext =
+ blurringArea.Init(shadowRect, shadowItem->mSpread,
+ blurRadius, twipsPerPixel, renderContext, aDirtyRect,
+ useSkipGfxRect ? &skipGfxRect : nullptr,
+ nsContextBoxBlur::FORCE_MASK);
+ if (!shadowContext)
+ continue;
+
+ MOZ_ASSERT(shadowContext == blurringArea.GetContext());
+
+ renderContext->Save();
+ renderContext->SetColor(gfxShadowColor);
+
+ // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur
+ // doesn't make any temporary surfaces if blur is 0 and it just returns the original
+ // surface? If we have no blur, we're painting this fill on the actual content surface
+ // (renderContext == shadowContext) which is why we set up the color and clip
+ // before doing this.
+
+ // We don't clip the border-box from the shadow, nor any other box.
+ // We assume that the native theme is going to paint over the shadow.
+
+ // Draw the widget shape
+ gfxContextMatrixAutoSaveRestore save(shadowContext);
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(nsPoint(shadowItem->mXOffset,
+ shadowItem->mYOffset),
+ aPresContext->AppUnitsPerDevPixel());
+ shadowContext->SetMatrix(
+ shadowContext->CurrentMatrix().Translate(devPixelOffset));
+
+ nsRect nativeRect = aDirtyRect;
+ nativeRect.MoveBy(-nsPoint(shadowItem->mXOffset, shadowItem->mYOffset));
+ nativeRect.IntersectRect(frameRect, nativeRect);
+ nsRenderingContext wrapperCtx(shadowContext);
+ aPresContext->GetTheme()->DrawWidgetBackground(&wrapperCtx, aForFrame,
+ styleDisplay->mAppearance, aFrameArea, nativeRect);
+
+ blurringArea.DoPaint();
+ renderContext->Restore();
+ } else {
+ renderContext->Save();
+
+ {
+ Rect innerClipRect = NSRectToRect(frameRect, twipsPerPixel);
+ if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
+ innerClipRect.Round();
+ }
+
+ // Clip out the interior of the frame's border edge so that the shadow
+ // is only painted outside that area.
+ RefPtr<PathBuilder> builder =
+ aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+ AppendRectToPath(builder, shadowGfxRectPlusBlur);
+ if (hasBorderRadius) {
+ AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
+ } else {
+ AppendRectToPath(builder, innerClipRect);
+ }
+ RefPtr<Path> path = builder->Finish();
+ renderContext->Clip(path);
+ }
+
+ // Clip the shadow so that we only get the part that applies to aForFrame.
+ nsRect fragmentClip = shadowRectPlusBlur;
+ if (!skipSides.IsEmpty()) {
+ if (skipSides.Left()) {
+ nscoord xmost = fragmentClip.XMost();
+ fragmentClip.x = aFrameArea.x;
+ fragmentClip.width = xmost - fragmentClip.x;
+ }
+ if (skipSides.Right()) {
+ nscoord xmost = fragmentClip.XMost();
+ nscoord overflow = xmost - aFrameArea.XMost();
+ if (overflow > 0) {
+ fragmentClip.width -= overflow;
+ }
+ }
+ if (skipSides.Top()) {
+ nscoord ymost = fragmentClip.YMost();
+ fragmentClip.y = aFrameArea.y;
+ fragmentClip.height = ymost - fragmentClip.y;
+ }
+ if (skipSides.Bottom()) {
+ nscoord ymost = fragmentClip.YMost();
+ nscoord overflow = ymost - aFrameArea.YMost();
+ if (overflow > 0) {
+ fragmentClip.height -= overflow;
+ }
+ }
+ }
+ fragmentClip = fragmentClip.Intersect(aDirtyRect);
+ renderContext->
+ Clip(NSRectToSnappedRect(fragmentClip,
+ aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget));
+
+ RectCornerRadii clipRectRadii;
+ if (hasBorderRadius) {
+ Float spreadDistance = shadowItem->mSpread / twipsPerPixel;
+
+ Float borderSizes[4];
+
+ borderSizes[NS_SIDE_LEFT] = spreadDistance;
+ borderSizes[NS_SIDE_TOP] = spreadDistance;
+ borderSizes[NS_SIDE_RIGHT] = spreadDistance;
+ borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
+
+ nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
+ &clipRectRadii);
+
+ }
+ nsContextBoxBlur::BlurRectangle(renderContext,
+ shadowRect,
+ twipsPerPixel,
+ hasBorderRadius ? &clipRectRadii : nullptr,
+ blurRadius,
+ gfxShadowColor,
+ aDirtyRect,
+ skipGfxRect);
+ renderContext->Restore();
+ }
+
+ }
+}
+
+void
+nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea)
+{
+ nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
+ if (!shadows)
+ return;
+ if (aForFrame->IsThemed() && aForFrame->GetContent() &&
+ !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetUncomposedDoc())) {
+ // There's no way of getting hold of a shape corresponding to a
+ // "padding-box" for native-themed widgets, so just don't draw
+ // inner box-shadows for them. But we allow chrome to paint inner
+ // box shadows since chrome can be aware of the platform theme.
+ return;
+ }
+
+ NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame ||
+ aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
+
+ Sides skipSides = aForFrame->GetSkipSides();
+ nsRect frameRect =
+ ::BoxDecorationRectForBorder(aForFrame, aFrameArea, skipSides);
+ nsRect paddingRect = frameRect;
+ nsMargin border = aForFrame->GetUsedBorder();
+ paddingRect.Deflate(border);
+
+ // Get any border radius, since box-shadow must also have rounded corners
+ // if the frame does.
+ nscoord twipsRadii[8];
+ nsSize sz = frameRect.Size();
+ bool hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
+ const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
+
+ RectCornerRadii innerRadii;
+ if (hasBorderRadius) {
+ RectCornerRadii borderRadii;
+
+ ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
+ Float borderSizes[4] = {
+ Float(border.top / twipsPerPixel),
+ Float(border.right / twipsPerPixel),
+ Float(border.bottom / twipsPerPixel),
+ Float(border.left / twipsPerPixel)
+ };
+ nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
+ &innerRadii);
+ }
+
+ for (uint32_t i = shadows->Length(); i > 0; --i) {
+ nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
+ if (!shadowItem->mInset)
+ continue;
+
+ // shadowPaintRect: the area to paint on the temp surface
+ // shadowClipRect: the area on the temporary surface within shadowPaintRect
+ // that we will NOT paint in
+ nscoord blurRadius = shadowItem->mRadius;
+ nsMargin blurMargin =
+ nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
+ nsRect shadowPaintRect = paddingRect;
+ shadowPaintRect.Inflate(blurMargin);
+
+ Rect shadowPaintGfxRect = NSRectToRect(shadowPaintRect, twipsPerPixel);
+ shadowPaintGfxRect.RoundOut();
+
+ // Round the spread radius to device pixels (by truncation).
+ // This mostly matches what we do for borders, except that we don't round
+ // up values between zero and one device pixels to one device pixel.
+ // This way of rounding is symmetric around zero, which makes sense for
+ // the spread radius.
+ int32_t spreadDistance = shadowItem->mSpread / twipsPerPixel;
+ nscoord spreadDistanceAppUnits = aPresContext->DevPixelsToAppUnits(spreadDistance);
+
+ nsRect shadowClipRect = paddingRect;
+ shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
+ shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
+
+ Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, twipsPerPixel);
+ shadowClipGfxRect.Round();
+
+ RectCornerRadii clipRectRadii;
+ if (hasBorderRadius) {
+ // Calculate the radii the inner clipping rect will have
+ Float borderSizes[4] = {0, 0, 0, 0};
+
+ // See PaintBoxShadowOuter and bug 514670
+ if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
+ borderSizes[NS_SIDE_LEFT] = spreadDistance;
+ }
+
+ if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
+ borderSizes[NS_SIDE_TOP] = spreadDistance;
+ }
+
+ if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
+ borderSizes[NS_SIDE_RIGHT] = spreadDistance;
+ }
+
+ if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
+ borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
+ }
+
+ nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
+ &clipRectRadii);
+ }
+
+ // Set the "skip rect" to the area within the frame that we don't paint in,
+ // including after blurring.
+ nsRect skipRect = shadowClipRect;
+ skipRect.Deflate(blurMargin);
+ gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
+ if (hasBorderRadius) {
+ skipGfxRect.Deflate(gfxMargin(
+ std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
+ std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
+ }
+
+ // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
+ // unchanged. And by construction the gfxSkipRect is not touched by the
+ // rendered shadow (even after blurring), so those pixels must be completely
+ // transparent in the shadow, so drawing them changes nothing.
+ gfxContext* renderContext = aRenderingContext.ThebesContext();
+ DrawTarget* drawTarget = renderContext->GetDrawTarget();
+ nsContextBoxBlur blurringArea;
+
+ // Clip the context to the area of the frame's padding rect, so no part of the
+ // shadow is painted outside. Also cut out anything beyond where the inset shadow
+ // will be.
+ Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
+ shadowGfxRect.Round();
+
+ // Set the shadow color; if not specified, use the foreground color
+ Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
+ shadowItem->mColor :
+ aForFrame->StyleColor()->mColor);
+
+ renderContext->Save();
+
+ // This clips the outside border radius.
+ // clipRectRadii is the border radius inside the inset shadow.
+ if (hasBorderRadius) {
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
+ renderContext->Clip(roundedRect);
+ } else {
+ renderContext->Clip(shadowGfxRect);
+ }
+
+ nsContextBoxBlur insetBoxBlur;
+ gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
+ Point shadowOffset(shadowItem->mXOffset / twipsPerPixel,
+ shadowItem->mYOffset / twipsPerPixel);
+
+ insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
+ shadowClipGfxRect, shadowColor,
+ blurRadius, spreadDistanceAppUnits,
+ twipsPerPixel, hasBorderRadius,
+ clipRectRadii, ToRect(skipGfxRect),
+ shadowOffset);
+ renderContext->Restore();
+ }
+}
+
+/* static */
+nsCSSRendering::PaintBGParams
+nsCSSRendering::PaintBGParams::ForAllLayers(nsPresContext& aPresCtx,
+ nsRenderingContext& aRenderingCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame *aFrame,
+ uint32_t aPaintFlags)
+{
+ MOZ_ASSERT(aFrame);
+
+ PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
+ aPaintFlags, -1, CompositionOp::OP_OVER);
+
+ return result;
+}
+
+/* static */
+nsCSSRendering::PaintBGParams
+nsCSSRendering::PaintBGParams::ForSingleLayer(nsPresContext& aPresCtx,
+ nsRenderingContext& aRenderingCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame *aFrame,
+ uint32_t aPaintFlags,
+ int32_t aLayer,
+ CompositionOp aCompositionOp)
+{
+ MOZ_ASSERT(aFrame && (aLayer != -1));
+
+ PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
+ aPaintFlags, aLayer, aCompositionOp);
+
+ return result;
+}
+
+DrawResult
+nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
+{
+ PROFILER_LABEL("nsCSSRendering", "PaintBackground",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ NS_PRECONDITION(aParams.frame,
+ "Frame is expected to be provided to PaintBackground");
+
+ nsStyleContext *sc;
+ if (!FindBackground(aParams.frame, &sc)) {
+ // We don't want to bail out if moz-appearance is set on a root
+ // node. If it has a parent content node, bail because it's not
+ // a root, otherwise keep going in order to let the theme stuff
+ // draw the background. The canvas really should be drawing the
+ // bg, but there's no way to hook that up via css.
+ if (!aParams.frame->StyleDisplay()->mAppearance) {
+ return DrawResult::SUCCESS;
+ }
+
+ nsIContent* content = aParams.frame->GetContent();
+ if (!content || content->GetParent()) {
+ return DrawResult::SUCCESS;
+ }
+
+ sc = aParams.frame->StyleContext();
+ }
+
+ return PaintBackgroundWithSC(aParams, sc, *aParams.frame->StyleBorder());
+}
+
+static bool
+IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide)
+{
+ if (aBorder.GetComputedBorder().Side(aSide) == 0)
+ return true;
+ switch (aBorder.GetBorderStyle(aSide)) {
+ case NS_STYLE_BORDER_STYLE_SOLID:
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ case NS_STYLE_BORDER_STYLE_INSET:
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ break;
+ default:
+ return false;
+ }
+
+ // If we're using a border image, assume it's not fully opaque,
+ // because we may not even have the image loaded at this point, and
+ // even if we did, checking whether the relevant tile is fully
+ // opaque would be too much work.
+ if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
+ return false;
+
+ StyleComplexColor color = aBorder.mBorderColor[aSide];
+ // We don't know the foreground color here, so if it's being used
+ // we must assume it might be transparent.
+ if (!color.IsNumericColor()) {
+ return false;
+ }
+ return NS_GET_A(color.mColor) == 255;
+}
+
+/**
+ * Returns true if all border edges are either missing or opaque.
+ */
+static bool
+IsOpaqueBorder(const nsStyleBorder& aBorder)
+{
+ if (aBorder.mBorderColors)
+ return false;
+ NS_FOR_CSS_SIDES(i) {
+ if (!IsOpaqueBorderEdge(aBorder, i))
+ return false;
+ }
+ return true;
+}
+
+static inline void
+SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
+ nscoord aAppUnitsPerPixel,
+ /* OUT: */
+ nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
+{
+ aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
+
+ // Compute the Thebes equivalent of the dirtyRect.
+ *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
+ NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
+ "converted dirty rect should not be empty");
+ MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
+ "second should be empty if first is");
+}
+
+/* static */ void
+nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
+ bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
+ /* out */ ImageLayerClipState* aClipState)
+{
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aBorderArea.
+ Sides skipSides = aForFrame->GetSkipSides();
+ nsRect clipBorderArea =
+ ::BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
+
+ bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
+ clipBorderArea, aClipState->mRadii);
+
+ uint8_t backgroundClip = aLayer.mClip;
+
+ bool isSolidBorder =
+ aWillPaintBorder && IsOpaqueBorder(aBorder);
+ if (isSolidBorder && backgroundClip == NS_STYLE_IMAGELAYER_CLIP_BORDER) {
+ // If we have rounded corners, we need to inflate the background
+ // drawing area a bit to avoid seams between the border and
+ // background.
+ backgroundClip = haveRoundedCorners ?
+ NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_IMAGELAYER_CLIP_PADDING;
+ }
+
+ aClipState->mBGClipArea = clipBorderArea;
+ aClipState->mHasAdditionalBGClipArea = false;
+ aClipState->mCustomClip = false;
+
+ if (aForFrame->GetType() == nsGkAtoms::scrollFrame &&
+ NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment) {
+ // As of this writing, this is still in discussion in the CSS Working Group
+ // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
+
+ // The rectangle for 'background-clip' scrolls with the content,
+ // but the background is also clipped at a non-scrolling 'padding-box'
+ // like the content. (See below.)
+ // Therefore, only 'content-box' makes a difference here.
+ if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
+ // Clip at a rectangle attached to the scrolled content.
+ aClipState->mHasAdditionalBGClipArea = true;
+ aClipState->mAdditionalBGClipArea = nsRect(
+ aClipState->mBGClipArea.TopLeft()
+ + scrollableFrame->GetScrolledFrame()->GetPosition()
+ // For the dir=rtl case:
+ + scrollableFrame->GetScrollRange().TopLeft(),
+ scrollableFrame->GetScrolledRect().Size());
+ nsMargin padding = aForFrame->GetUsedPadding();
+ // padding-bottom is ignored on scrollable frames:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
+ padding.bottom = 0;
+ padding.ApplySkipSides(skipSides);
+ aClipState->mAdditionalBGClipArea.Deflate(padding);
+ }
+
+ // Also clip at a non-scrolling, rounded-corner 'padding-box',
+ // same as the scrolled content because of the 'overflow' property.
+ backgroundClip = NS_STYLE_IMAGELAYER_CLIP_PADDING;
+ }
+
+ if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER &&
+ backgroundClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ nsMargin border = aForFrame->GetUsedBorder();
+ if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING) {
+ // Reduce |border| by 1px (device pixels) on all sides, if
+ // possible, so that we don't get antialiasing seams between the
+ // background and border.
+ border.top = std::max(0, border.top - aAppUnitsPerPixel);
+ border.right = std::max(0, border.right - aAppUnitsPerPixel);
+ border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
+ border.left = std::max(0, border.left - aAppUnitsPerPixel);
+ } else if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_PADDING) {
+ NS_ASSERTION(backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT,
+ "unexpected background-clip");
+ border += aForFrame->GetUsedPadding();
+ }
+ border.ApplySkipSides(skipSides);
+ aClipState->mBGClipArea.Deflate(border);
+
+ if (haveRoundedCorners) {
+ nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
+ }
+ }
+
+ if (haveRoundedCorners) {
+ auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, &aClipState->mClippedRadii);
+ aClipState->mHasRoundedCorners = true;
+ } else {
+ aClipState->mHasRoundedCorners = false;
+ }
+
+
+ if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
+ // Do the intersection here to account for the fast path(?) below.
+ aClipState->mBGClipArea =
+ aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
+ aClipState->mHasAdditionalBGClipArea = false;
+ }
+
+ SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
+ &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx);
+}
+
+static void
+SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
+ gfxContext *aCtx, nscoord aAppUnitsPerPixel,
+ gfxContextAutoSaveRestore* aAutoSR)
+{
+ if (aClipState.mDirtyRectGfx.IsEmpty()) {
+ // Our caller won't draw anything under this condition, so no need
+ // to set more up.
+ return;
+ }
+
+ if (aClipState.mCustomClip) {
+ // We don't support custom clips and rounded corners, arguably a bug, but
+ // table painting seems to depend on it.
+ return;
+ }
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+
+ // If we have rounded corners, clip all subsequent drawing to the
+ // rounded rectangle defined by bgArea and bgRadii (we don't know
+ // whether the rounded corners intrude on the dirtyRect or not).
+ // Do not do this if we have a caller-provided clip rect --
+ // as above with bgArea, arguably a bug, but table painting seems
+ // to depend on it.
+
+ if (aClipState.mHasAdditionalBGClipArea) {
+ gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
+ aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+ bgAreaGfx.Condition();
+
+ aAutoSR->EnsureSaved(aCtx);
+ aCtx->NewPath();
+ aCtx->Rectangle(bgAreaGfx, true);
+ aCtx->Clip();
+ }
+
+ if (aClipState.mHasRoundedCorners) {
+ Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+
+ if (bgAreaGfx.IsEmpty()) {
+ // I think it's become possible to hit this since
+ // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
+ NS_WARNING("converted background area should not be empty");
+ // Make our caller not do anything.
+ aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
+ return;
+ }
+
+ aAutoSR->EnsureSaved(aCtx);
+
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
+ aCtx->Clip(roundedRect);
+ }
+}
+
+static void
+DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
+ gfxContext *aCtx, nscoord aAppUnitsPerPixel)
+{
+ if (aClipState.mDirtyRectGfx.IsEmpty()) {
+ // Our caller won't draw anything under this condition, so no need
+ // to set more up.
+ return;
+ }
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+
+ // We don't support custom clips and rounded corners, arguably a bug, but
+ // table painting seems to depend on it.
+ if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
+ aCtx->NewPath();
+ aCtx->Rectangle(aClipState.mDirtyRectGfx, true);
+ aCtx->Fill();
+ return;
+ }
+
+ Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
+ bgAreaGfx.Round();
+
+ if (bgAreaGfx.IsEmpty()) {
+ // I think it's become possible to hit this since
+ // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
+ NS_WARNING("converted background area should not be empty");
+ // Make our caller not do anything.
+ aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
+ return;
+ }
+
+ aCtx->Save();
+ gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectGfx);
+
+ aCtx->NewPath();
+ aCtx->Rectangle(dirty, true);
+ aCtx->Clip();
+
+ if (aClipState.mHasAdditionalBGClipArea) {
+ gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
+ aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
+ bgAdditionalAreaGfx.Round();
+ bgAdditionalAreaGfx.Condition();
+ aCtx->NewPath();
+ aCtx->Rectangle(bgAdditionalAreaGfx, true);
+ aCtx->Clip();
+ }
+
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
+ aCtx->SetPath(roundedRect);
+ aCtx->Fill();
+ aCtx->Restore();
+}
+
+nscolor
+nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ nsIFrame* aFrame,
+ bool& aDrawBackgroundImage,
+ bool& aDrawBackgroundColor)
+{
+ aDrawBackgroundImage = true;
+ aDrawBackgroundColor = true;
+
+ const nsStyleVisibility* visibility = aStyleContext->StyleVisibility();
+
+ if (visibility->mColorAdjust != NS_STYLE_COLOR_ADJUST_EXACT &&
+ aFrame->HonorPrintBackgroundSettings()) {
+ aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
+ aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
+ }
+
+ const nsStyleBackground *bg = aStyleContext->StyleBackground();
+ nscolor bgColor;
+ if (aDrawBackgroundColor) {
+ bgColor =
+ aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color);
+ if (NS_GET_A(bgColor) == 0) {
+ aDrawBackgroundColor = false;
+ }
+ } else {
+ // If GetBackgroundColorDraw() is false, we are still expected to
+ // draw color in the background of any frame that's not completely
+ // transparent, but we are expected to use white instead of whatever
+ // color was specified.
+ bgColor = NS_RGB(255, 255, 255);
+ if (aDrawBackgroundImage || !bg->IsTransparent()) {
+ aDrawBackgroundColor = true;
+ } else {
+ bgColor = NS_RGBA(0,0,0,0);
+ }
+ }
+
+ // We can skip painting the background color if a background image is opaque.
+ nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
+ bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+ bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+ if (aDrawBackgroundColor &&
+ xFullRepeat && yFullRepeat &&
+ bg->BottomLayer().mImage.IsOpaque() &&
+ bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
+ aDrawBackgroundColor = false;
+ }
+
+ return bgColor;
+}
+
+static gfxFloat
+ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
+ gfxFloat aFillLength,
+ int32_t aAppUnitsPerPixel)
+{
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Percent:
+ return aCoord.GetPercentValue() * aFillLength;
+ case eStyleUnit_Coord:
+ return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
+ case eStyleUnit_Calc: {
+ const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
+ return calc->mPercent * aFillLength +
+ NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
+ }
+ default:
+ NS_WARNING("Unexpected coord unit");
+ return 0;
+ }
+}
+
+// Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
+// and a starting point for the gradient line aStart, find the endpoint of
+// the gradient line --- the intersection of the gradient line with a line
+// perpendicular to aAngle that passes through the farthest corner in the
+// direction aAngle.
+static gfxPoint
+ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
+ double aAngle,
+ const gfxSize& aBoxSize)
+{
+ double dx = cos(-aAngle);
+ double dy = sin(-aAngle);
+ gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
+ dy > 0 ? aBoxSize.height : 0);
+ gfxPoint delta = farthestCorner - aStart;
+ double u = delta.x*dy - delta.y*dx;
+ return farthestCorner + gfxPoint(-u*dy, u*dx);
+}
+
+// Compute the start and end points of the gradient line for a linear gradient.
+static void
+ComputeLinearGradientLine(nsPresContext* aPresContext,
+ nsStyleGradient* aGradient,
+ const gfxSize& aBoxSize,
+ gfxPoint* aLineStart,
+ gfxPoint* aLineEnd)
+{
+ if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
+ double angle;
+ if (aGradient->mAngle.IsAngleValue()) {
+ angle = aGradient->mAngle.GetAngleValueInRadians();
+ if (!aGradient->mLegacySyntax) {
+ angle = M_PI_2 - angle;
+ }
+ } else {
+ angle = -M_PI_2; // defaults to vertical gradient starting from top
+ }
+ gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
+ *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
+ *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
+ } else if (!aGradient->mLegacySyntax) {
+ float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
+ float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
+ double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
+ gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
+ *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
+ *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
+ } else {
+ int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
+ *aLineStart = gfxPoint(
+ ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
+ appUnitsPerPixel),
+ ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
+ appUnitsPerPixel));
+ if (aGradient->mAngle.IsAngleValue()) {
+ MOZ_ASSERT(aGradient->mLegacySyntax);
+ double angle = aGradient->mAngle.GetAngleValueInRadians();
+ *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
+ } else {
+ // No angle, the line end is just the reflection of the start point
+ // through the center of the box
+ *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
+ }
+ }
+}
+
+// Compute the start and end points of the gradient line for a radial gradient.
+// Also returns the horizontal and vertical radii defining the circle or
+// ellipse to use.
+static void
+ComputeRadialGradientLine(nsPresContext* aPresContext,
+ nsStyleGradient* aGradient,
+ const gfxSize& aBoxSize,
+ gfxPoint* aLineStart,
+ gfxPoint* aLineEnd,
+ double* aRadiusX,
+ double* aRadiusY)
+{
+ if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
+ // Default line start point is the center of the box
+ *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
+ } else {
+ int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
+ *aLineStart = gfxPoint(
+ ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
+ appUnitsPerPixel),
+ ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
+ appUnitsPerPixel));
+ }
+
+ // Compute gradient shape: the x and y radii of an ellipse.
+ double radiusX, radiusY;
+ double leftDistance = Abs(aLineStart->x);
+ double rightDistance = Abs(aBoxSize.width - aLineStart->x);
+ double topDistance = Abs(aLineStart->y);
+ double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
+ switch (aGradient->mSize) {
+ case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
+ radiusX = std::min(leftDistance, rightDistance);
+ radiusY = std::min(topDistance, bottomDistance);
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ radiusX = radiusY = std::min(radiusX, radiusY);
+ }
+ break;
+ case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
+ // Compute x and y distances to nearest corner
+ double offsetX = std::min(leftDistance, rightDistance);
+ double offsetY = std::min(topDistance, bottomDistance);
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ radiusX = radiusY = NS_hypot(offsetX, offsetY);
+ } else {
+ // maintain aspect ratio
+ radiusX = offsetX*M_SQRT2;
+ radiusY = offsetY*M_SQRT2;
+ }
+ break;
+ }
+ case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
+ radiusX = std::max(leftDistance, rightDistance);
+ radiusY = std::max(topDistance, bottomDistance);
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ radiusX = radiusY = std::max(radiusX, radiusY);
+ }
+ break;
+ case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
+ // Compute x and y distances to nearest corner
+ double offsetX = std::max(leftDistance, rightDistance);
+ double offsetY = std::max(topDistance, bottomDistance);
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ radiusX = radiusY = NS_hypot(offsetX, offsetY);
+ } else {
+ // maintain aspect ratio
+ radiusX = offsetX*M_SQRT2;
+ radiusY = offsetY*M_SQRT2;
+ }
+ break;
+ }
+ case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
+ int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
+ radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
+ aBoxSize.width, appUnitsPerPixel);
+ radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
+ aBoxSize.height, appUnitsPerPixel);
+ break;
+ }
+ default:
+ radiusX = radiusY = 0;
+ MOZ_ASSERT(false, "unknown radial gradient sizing method");
+ }
+ *aRadiusX = radiusX;
+ *aRadiusY = radiusY;
+
+ double angle;
+ if (aGradient->mAngle.IsAngleValue()) {
+ angle = aGradient->mAngle.GetAngleValueInRadians();
+ } else {
+ // Default angle is 0deg
+ angle = 0.0;
+ }
+
+ // The gradient line end point is where the gradient line intersects
+ // the ellipse.
+ *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
+}
+
+
+static float Interpolate(float aF1, float aF2, float aFrac)
+{
+ return aF1 + aFrac * (aF2 - aF1);
+}
+
+// Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
+// in unpremultiplied space, which is what SVG gradients and cairo
+// gradients expect.
+static Color
+InterpolateColor(const Color& aC1, const Color& aC2, float aFrac)
+{
+ double other = 1 - aFrac;
+ return Color(aC2.r*aFrac + aC1.r*other,
+ aC2.g*aFrac + aC1.g*other,
+ aC2.b*aFrac + aC1.b*other,
+ aC2.a*aFrac + aC1.a*other);
+}
+
+static nscoord
+FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
+{
+ NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
+ double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
+ return NSToCoordRound(multiples*aTileDim + aTilePos);
+}
+
+static gfxFloat
+LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
+ const gfxPoint& aGradientEnd,
+ const gfxPoint& aPoint)
+{
+ gfxPoint d = aGradientEnd - aGradientStart;
+ gfxPoint p = aPoint - aGradientStart;
+ /**
+ * Compute a parameter t such that a line perpendicular to the
+ * d vector, passing through aGradientStart + d*t, also
+ * passes through aPoint.
+ *
+ * t is given by
+ * (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
+ *
+ * Solving for t we get
+ * numerator = d.x*p.x + d.y*p.y
+ * denominator = d.x^2 + d.y^2
+ * t = numerator/denominator
+ *
+ * In nsCSSRendering::PaintGradient we know the length of d
+ * is not zero.
+ */
+ double numerator = d.x * p.x + d.y * p.y;
+ double denominator = d.x * d.x + d.y * d.y;
+ return numerator / denominator;
+}
+
+static bool
+RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
+ const gfxMatrix& aPatternMatrix,
+ const nsTArray<ColorStop>& aStops,
+ const gfxPoint& aGradientStart,
+ const gfxPoint& aGradientEnd,
+ Color* aOutEdgeColor)
+{
+ gfxFloat topLeft = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopLeft()));
+ gfxFloat topRight = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopRight()));
+ gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomLeft()));
+ gfxFloat bottomRight = LinearGradientStopPositionForPoint(
+ aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomRight()));
+
+ const ColorStop& firstStop = aStops[0];
+ if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
+ bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
+ *aOutEdgeColor = firstStop.mColor;
+ return true;
+ }
+
+ const ColorStop& lastStop = aStops.LastElement();
+ if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
+ bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
+ *aOutEdgeColor = lastStop.mColor;
+ return true;
+ }
+
+ return false;
+}
+
+static void ResolveMidpoints(nsTArray<ColorStop>& stops)
+{
+ for (size_t x = 1; x < stops.Length() - 1;) {
+ if (!stops[x].mIsMidpoint) {
+ x++;
+ continue;
+ }
+
+ Color color1 = stops[x-1].mColor;
+ Color color2 = stops[x+1].mColor;
+ float offset1 = stops[x-1].mPosition;
+ float offset2 = stops[x+1].mPosition;
+ float offset = stops[x].mPosition;
+ // check if everything coincides. If so, ignore the midpoint.
+ if (offset - offset1 == offset2 - offset) {
+ stops.RemoveElementAt(x);
+ continue;
+ }
+
+ // Check if we coincide with the left colorstop.
+ if (offset1 == offset) {
+ // Morph the midpoint to a regular stop with the color of the next
+ // color stop.
+ stops[x].mColor = color2;
+ stops[x].mIsMidpoint = false;
+ continue;
+ }
+
+ // Check if we coincide with the right colorstop.
+ if (offset2 == offset) {
+ // Morph the midpoint to a regular stop with the color of the previous
+ // color stop.
+ stops[x].mColor = color1;
+ stops[x].mIsMidpoint = false;
+ continue;
+ }
+
+ float midpoint = (offset - offset1) / (offset2 - offset1);
+ ColorStop newStops[9];
+ if (midpoint > .5f) {
+ for (size_t y = 0; y < 7; y++) {
+ newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
+ }
+
+ newStops[7].mPosition = offset + (offset2 - offset) / 3;
+ newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
+ } else {
+ newStops[0].mPosition = offset1 + (offset - offset1) / 3;
+ newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
+
+ for (size_t y = 0; y < 7; y++) {
+ newStops[y+2].mPosition = offset + (offset2 - offset) * y / 13;
+ }
+ }
+ // calculate colors
+
+ for (size_t y = 0; y < 9; y++) {
+ // Calculate the intermediate color stops per the formula of the CSS images
+ // spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
+ // 9 points were chosen since it is the minimum number of stops that always
+ // give the smoothest appearace regardless of midpoint position and difference
+ // in luminance of the end points.
+ float relativeOffset = (newStops[y].mPosition - offset1) / (offset2 - offset1);
+ float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
+
+ gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
+ gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
+ gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
+ gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
+
+ newStops[y].mColor = Color(red, green, blue, alpha);
+ }
+
+ stops.ReplaceElementsAt(x, 1, newStops, 9);
+ x += 9;
+ }
+}
+
+static Color
+Premultiply(const Color& aColor)
+{
+ gfx::Float a = aColor.a;
+ return Color(aColor.r * a, aColor.g * a, aColor.b * a, a);
+}
+
+static Color
+Unpremultiply(const Color& aColor)
+{
+ gfx::Float a = aColor.a;
+ return (a > 0.f)
+ ? Color(aColor.r / a, aColor.g / a, aColor.b / a, a)
+ : aColor;
+}
+
+static Color
+TransparentColor(Color aColor) {
+ aColor.a = 0;
+ return aColor;
+}
+
+// Adjusts and adds color stops in such a way that drawing the gradient with
+// unpremultiplied interpolation looks nearly the same as if it were drawn with
+// premultiplied interpolation.
+static const float kAlphaIncrementPerGradientStep = 0.1f;
+static void
+ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops)
+{
+ for (size_t x = 1; x < aStops.Length(); x++) {
+ const ColorStop leftStop = aStops[x - 1];
+ const ColorStop rightStop = aStops[x];
+
+ // if the left and right stop have the same alpha value, we don't need
+ // to do anything
+ if (leftStop.mColor.a == rightStop.mColor.a) {
+ continue;
+ }
+
+ // Is the stop on the left 100% transparent? If so, have it adopt the color
+ // of the right stop
+ if (leftStop.mColor.a == 0) {
+ aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
+ continue;
+ }
+
+ // Is the stop on the right completely transparent?
+ // If so, duplicate it and assign it the color on the left.
+ if (rightStop.mColor.a == 0) {
+ ColorStop newStop = rightStop;
+ newStop.mColor = TransparentColor(leftStop.mColor);
+ aStops.InsertElementAt(x, newStop);
+ x++;
+ continue;
+ }
+
+ // Now handle cases where one or both of the stops are partially transparent.
+ if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
+ Color premulLeftColor = Premultiply(leftStop.mColor);
+ Color premulRightColor = Premultiply(rightStop.mColor);
+ // Calculate how many extra steps. We do a step per 10% transparency.
+ size_t stepCount = NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / kAlphaIncrementPerGradientStep);
+ for (size_t y = 1; y < stepCount; y++) {
+ float frac = static_cast<float>(y) / stepCount;
+ ColorStop newStop(Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
+ Unpremultiply(InterpolateColor(premulLeftColor, premulRightColor, frac)));
+ aStops.InsertElementAt(x, newStop);
+ x++;
+ }
+ }
+ }
+}
+
+static ColorStop
+InterpolateColorStop(const ColorStop& aFirst, const ColorStop& aSecond,
+ double aPosition, const Color& aDefault)
+{
+ MOZ_ASSERT(aFirst.mPosition <= aPosition);
+ MOZ_ASSERT(aPosition <= aSecond.mPosition);
+
+ double delta = aSecond.mPosition - aFirst.mPosition;
+
+ if (delta < 1e-6) {
+ return ColorStop(aPosition, false, aDefault);
+ }
+
+ return ColorStop(aPosition, false,
+ Unpremultiply(InterpolateColor(Premultiply(aFirst.mColor),
+ Premultiply(aSecond.mColor),
+ (aPosition - aFirst.mPosition) / delta)));
+}
+
+// Clamp and extend the given ColorStop array in-place to fit exactly into the
+// range [0, 1].
+static void
+ClampColorStops(nsTArray<ColorStop>& aStops)
+{
+ MOZ_ASSERT(aStops.Length() > 0);
+
+ // If all stops are outside the range, then get rid of everything and replace
+ // with a single colour.
+ if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
+ aStops.LastElement().mPosition < 0) {
+ Color c = aStops[0].mPosition > 1 ? aStops[0].mColor : aStops.LastElement().mColor;
+ aStops.Clear();
+ aStops.AppendElement(ColorStop(0, false, c));
+ return;
+ }
+
+ // Create the 0 and 1 points if they fall in the range of |aStops|, and discard
+ // all stops outside the range [0, 1].
+ // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
+ // those stops. This should be fine for the current user(s) of this function.
+ for (size_t i = aStops.Length() - 1; i > 0; i--) {
+ if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
+ // Add a point to position 1.
+ aStops[i] = InterpolateColorStop(aStops[i - 1], aStops[i],
+ /* aPosition = */ 1,
+ aStops[i - 1].mColor);
+ // Remove all the elements whose position is greater than 1.
+ aStops.RemoveElementsAt(i + 1, aStops.Length() - (i + 1));
+ }
+ if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
+ // Add a point to position 0.
+ aStops[i - 1] = InterpolateColorStop(aStops[i - 1], aStops[i],
+ /* aPosition = */ 0,
+ aStops[i].mColor);
+ // Remove all of the preceding stops -- they are all negative.
+ aStops.RemoveElementsAt(0, i - 1);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(aStops[0].mPosition >= -1e6);
+ MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
+
+ // The end points won't exist yet if they don't fall in the original range of
+ // |aStops|. Create them if needed.
+ if (aStops[0].mPosition > 0) {
+ aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
+ }
+ if (aStops.LastElement().mPosition < 1) {
+ aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
+ }
+}
+
+void
+nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsStyleGradient* aGradient,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFillArea,
+ const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc,
+ const nsSize& aIntrinsicSize)
+{
+ PROFILER_LABEL("nsCSSRendering", "PaintGradient",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
+ if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
+ return;
+ }
+
+ gfxContext *ctx = aRenderingContext.ThebesContext();
+ nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+ gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
+ gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
+
+ bool cellContainsFill = aDest.Contains(aFillArea);
+
+ // Compute "gradient line" start and end relative to the intrinsic size of
+ // the gradient.
+ gfxPoint lineStart, lineEnd;
+ double radiusX = 0, radiusY = 0; // for radial gradients only
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
+ ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
+ &lineStart, &lineEnd);
+ } else {
+ ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
+ &lineStart, &lineEnd, &radiusX, &radiusY);
+ }
+ // Avoid sending Infs or Nans to downwind draw targets.
+ if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
+ lineStart = lineEnd = gfxPoint(0, 0);
+ }
+ gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
+ lineEnd.y - lineStart.y);
+
+ MOZ_ASSERT(aGradient->mStops.Length() >= 2,
+ "The parser should reject gradients with less than two stops");
+
+ // Build color stop array and compute stop positions
+ nsTArray<ColorStop> stops;
+ // If there is a run of stops before stop i that did not have specified
+ // positions, then this is the index of the first stop in that run, otherwise
+ // it's -1.
+ int32_t firstUnsetPosition = -1;
+ for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
+ const nsStyleGradientStop& stop = aGradient->mStops[i];
+ double position;
+ switch (stop.mLocation.GetUnit()) {
+ case eStyleUnit_None:
+ if (i == 0) {
+ // First stop defaults to position 0.0
+ position = 0.0;
+ } else if (i == aGradient->mStops.Length() - 1) {
+ // Last stop defaults to position 1.0
+ position = 1.0;
+ } else {
+ // Other stops with no specified position get their position assigned
+ // later by interpolation, see below.
+ // Remeber where the run of stops with no specified position starts,
+ // if it starts here.
+ if (firstUnsetPosition < 0) {
+ firstUnsetPosition = i;
+ }
+ stops.AppendElement(ColorStop(0, stop.mIsInterpolationHint,
+ Color::FromABGR(stop.mColor)));
+ continue;
+ }
+ break;
+ case eStyleUnit_Percent:
+ position = stop.mLocation.GetPercentValue();
+ break;
+ case eStyleUnit_Coord:
+ position = lineLength < 1e-6 ? 0.0 :
+ stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
+ break;
+ case eStyleUnit_Calc:
+ nsStyleCoord::Calc *calc;
+ calc = stop.mLocation.GetCalcValue();
+ position = calc->mPercent +
+ ((lineLength < 1e-6) ? 0.0 :
+ (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown stop position type");
+ }
+
+ if (i > 0) {
+ // Prevent decreasing stop positions by advancing this position
+ // to the previous stop position, if necessary
+ position = std::max(position, stops[i - 1].mPosition);
+ }
+ stops.AppendElement(ColorStop(position, stop.mIsInterpolationHint,
+ Color::FromABGR(stop.mColor)));
+ if (firstUnsetPosition > 0) {
+ // Interpolate positions for all stops that didn't have a specified position
+ double p = stops[firstUnsetPosition - 1].mPosition;
+ double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
+ for (uint32_t j = firstUnsetPosition; j < i; ++j) {
+ p += d;
+ stops[j].mPosition = p;
+ }
+ firstUnsetPosition = -1;
+ }
+ }
+
+ // If a non-repeating linear gradient is axis-aligned and there are no gaps
+ // between tiles, we can optimise away most of the work by converting to a
+ // repeating linear gradient and filling the whole destination rect at once.
+ bool forceRepeatToCoverTiles =
+ aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
+ (lineStart.x == lineEnd.x) != (lineStart.y == lineEnd.y) &&
+ aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
+ !aGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
+
+ gfxMatrix matrix;
+ if (forceRepeatToCoverTiles) {
+ // Length of the source rectangle along the gradient axis.
+ double rectLen;
+ // The position of the start of the rectangle along the gradient.
+ double offset;
+
+ // The gradient line is "backwards". Flip the line upside down to make
+ // things easier, and then rotate the matrix to turn everything back the
+ // right way up.
+ if (lineStart.x > lineEnd.x || lineStart.y > lineEnd.y) {
+ std::swap(lineStart, lineEnd);
+ matrix.Scale(-1, -1);
+ }
+
+ // Fit the gradient line exactly into the source rect.
+ // aSrc is relative to aIntrinsincSize.
+ // srcRectDev will be relative to srcSize, so in the same coordinate space
+ // as lineStart / lineEnd.
+ gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
+ CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
+ if (lineStart.x != lineEnd.x) {
+ rectLen = srcRectDev.width;
+ offset = (srcRectDev.x - lineStart.x) / lineLength;
+ lineStart.x = srcRectDev.x;
+ lineEnd.x = srcRectDev.XMost();
+ } else {
+ rectLen = srcRectDev.height;
+ offset = (srcRectDev.y - lineStart.y) / lineLength;
+ lineStart.y = srcRectDev.y;
+ lineEnd.y = srcRectDev.YMost();
+ }
+
+ // Adjust gradient stop positions for the new gradient line.
+ double scale = lineLength / rectLen;
+ for (size_t i = 0; i < stops.Length(); i++) {
+ stops[i].mPosition = (stops[i].mPosition - offset) * fabs(scale);
+ }
+
+ // Clamp or extrapolate gradient stops to exactly [0, 1].
+ ClampColorStops(stops);
+
+ lineLength = rectLen;
+ }
+
+ // Eliminate negative-position stops if the gradient is radial.
+ double firstStop = stops[0].mPosition;
+ if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
+ if (aGradient->mRepeating) {
+ // Choose an instance of the repeated pattern that gives us all positive
+ // stop-offsets.
+ double lastStop = stops[stops.Length() - 1].mPosition;
+ double stopDelta = lastStop - firstStop;
+ // If all the stops are in approximately the same place then logic below
+ // will kick in that makes us draw just the last stop color, so don't
+ // try to do anything in that case. We certainly need to avoid
+ // dividing by zero.
+ if (stopDelta >= 1e-6) {
+ double instanceCount = ceil(-firstStop/stopDelta);
+ // Advance stops by instanceCount multiples of the period of the
+ // repeating gradient.
+ double offset = instanceCount*stopDelta;
+ for (uint32_t i = 0; i < stops.Length(); i++) {
+ stops[i].mPosition += offset;
+ }
+ }
+ } else {
+ // Move negative-position stops to position 0.0. We may also need
+ // to set the color of the stop to the color the gradient should have
+ // at the center of the ellipse.
+ for (uint32_t i = 0; i < stops.Length(); i++) {
+ double pos = stops[i].mPosition;
+ if (pos < 0.0) {
+ stops[i].mPosition = 0.0;
+ // If this is the last stop, we don't need to adjust the color,
+ // it will fill the entire area.
+ if (i < stops.Length() - 1) {
+ double nextPos = stops[i + 1].mPosition;
+ // If nextPos is approximately equal to pos, then we don't
+ // need to adjust the color of this stop because it's
+ // not going to be displayed.
+ // If nextPos is negative, we don't need to adjust the color of
+ // this stop since it's not going to be displayed because
+ // nextPos will also be moved to 0.0.
+ if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
+ // Compute how far the new position 0.0 is along the interval
+ // between pos and nextPos.
+ // XXX Color interpolation (in cairo, too) should use the
+ // CSS 'color-interpolation' property!
+ float frac = float((0.0 - pos)/(nextPos - pos));
+ stops[i].mColor =
+ InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
+ }
+ }
+ }
+ }
+ }
+ firstStop = stops[0].mPosition;
+ MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
+ }
+
+ if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) {
+ // Direct2D can only handle a particular class of radial gradients because
+ // of the way the it specifies gradients. Setting firstStop to 0, when we
+ // can, will help us stay on the fast path. Currently we don't do this
+ // for repeating gradients but we could by adjusting the stop collection
+ // to start at 0
+ firstStop = 0;
+ }
+
+ double lastStop = stops[stops.Length() - 1].mPosition;
+ // Cairo gradients must have stop positions in the range [0, 1]. So,
+ // stop positions will be normalized below by subtracting firstStop and then
+ // multiplying by stopScale.
+ double stopScale;
+ double stopOrigin = firstStop;
+ double stopEnd = lastStop;
+ double stopDelta = lastStop - firstStop;
+ bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
+ (radiusX < 1e-6 || radiusY < 1e-6);
+ if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
+ // Stops are all at the same place. Map all stops to 0.0.
+ // For repeating radial gradients, or for any radial gradients with
+ // a zero radius, we need to fill with the last stop color, so just set
+ // both radii to 0.
+ if (aGradient->mRepeating || zeroRadius) {
+ radiusX = radiusY = 0.0;
+ }
+ stopDelta = 0.0;
+ lastStop = firstStop;
+ }
+
+ // Don't normalize non-repeating or degenerate gradients below 0..1
+ // This keeps the gradient line as large as the box and doesn't
+ // lets us avoiding having to get padding correct for stops
+ // at 0 and 1
+ if (!aGradient->mRepeating || stopDelta == 0.0) {
+ stopOrigin = std::min(stopOrigin, 0.0);
+ stopEnd = std::max(stopEnd, 1.0);
+ }
+ stopScale = 1.0/(stopEnd - stopOrigin);
+
+ // Create the gradient pattern.
+ RefPtr<gfxPattern> gradientPattern;
+ gfxPoint gradientStart;
+ gfxPoint gradientEnd;
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
+ // Compute the actual gradient line ends we need to pass to cairo after
+ // stops have been normalized.
+ gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
+ gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
+ gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
+ gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
+
+ if (stopDelta == 0.0) {
+ // Stops are all at the same place. For repeating gradients, this will
+ // just paint the last stop color. We don't need to do anything.
+ // For non-repeating gradients, this should render as two colors, one
+ // on each "side" of the gradient line segment, which is a point. All
+ // our stops will be at 0.0; we just need to set the direction vector
+ // correctly.
+ gradientEnd = gradientStart + (lineEnd - lineStart);
+ gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
+ }
+
+ gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
+ gradientEnd.x, gradientEnd.y);
+ } else {
+ NS_ASSERTION(firstStop >= 0.0,
+ "Negative stops not allowed for radial gradients");
+
+ // To form an ellipse, we'll stretch a circle vertically, if necessary.
+ // So our radii are based on radiusX.
+ double innerRadius = radiusX*stopOrigin;
+ double outerRadius = radiusX*stopEnd;
+ if (stopDelta == 0.0) {
+ // Stops are all at the same place. See above (except we now have
+ // the inside vs. outside of an ellipse).
+ outerRadius = innerRadius + 1;
+ }
+ gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
+ lineStart.x, lineStart.y, outerRadius);
+ if (radiusX != radiusY) {
+ // Stretch the circles into ellipses vertically by setting a transform
+ // in the pattern.
+ // Recall that this is the transform from user space to pattern space.
+ // So to stretch the ellipse by factor of P vertically, we scale
+ // user coordinates by 1/P.
+ matrix.Translate(lineStart);
+ matrix.Scale(1.0, radiusX/radiusY);
+ matrix.Translate(-lineStart);
+ }
+ }
+ // Use a pattern transform to take account of source and dest rects
+ matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
+ aPresContext->CSSPixelsToDevPixels(aSrc.y)));
+ matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
+ gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
+ gradientPattern->SetMatrix(matrix);
+
+ if (gradientPattern->CairoStatus())
+ return;
+
+ if (stopDelta == 0.0) {
+ // Non-repeating gradient with all stops in same place -> just add
+ // first stop and last stop, both at position 0.
+ // Repeating gradient with all stops in the same place, or radial
+ // gradient with radius of 0 -> just paint the last stop color.
+ // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
+ Color firstColor(stops[0].mColor);
+ Color lastColor(stops.LastElement().mColor);
+ stops.Clear();
+
+ if (!aGradient->mRepeating && !zeroRadius) {
+ stops.AppendElement(ColorStop(firstStop, false, firstColor));
+ }
+ stops.AppendElement(ColorStop(firstStop, false, lastColor));
+ }
+
+ ResolveMidpoints(stops);
+ ResolvePremultipliedAlpha(stops);
+
+ bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
+
+ // Now set normalized color stops in pattern.
+ // Offscreen gradient surface cache (not a tile):
+ // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
+ // which is a lookup table used to evaluate the gradient. This surface can use
+ // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
+ // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
+ // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
+ nsTArray<gfx::GradientStop> rawStops(stops.Length());
+ rawStops.SetLength(stops.Length());
+ for(uint32_t i = 0; i < stops.Length(); i++) {
+ rawStops[i].color = stops[i].mColor;
+ rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
+ }
+ RefPtr<mozilla::gfx::GradientStops> gs =
+ gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
+ rawStops,
+ isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
+ gradientPattern->SetColorStops(gs);
+
+ // Paint gradient tiles. This isn't terribly efficient, but doing it this
+ // way is simple and sure to get pixel-snapping right. We could speed things
+ // up by drawing tiles into temporary surfaces and copying those to the
+ // destination, but after pixel-snapping tiles may not all be the same size.
+ nsRect dirty;
+ if (!dirty.IntersectRect(aDirtyRect, aFillArea))
+ return;
+
+ gfxRect areaToFill =
+ nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
+ gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
+ dirtyAreaToFill.RoundOut();
+
+ gfxMatrix ctm = ctx->CurrentMatrix();
+ bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
+
+ // xStart/yStart are the top-left corner of the top-left tile.
+ nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
+ nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
+ nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
+ nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
+
+ // x and y are the top-left corner of the tile to draw
+ for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
+ for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
+ // The coordinates of the tile
+ gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
+ nsRect(x, y, aDest.width, aDest.height),
+ appUnitsPerDevPixel);
+ // The actual area to fill with this tile is the intersection of this
+ // tile with the overall area we're supposed to be filling
+ gfxRect fillRect =
+ forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
+ // Try snapping the fill rect. Snap its top-left and bottom-right
+ // independently to preserve the orientation.
+ gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
+ gfxPoint snappedFillRectTopRight = fillRect.TopRight();
+ gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
+ // Snap three points instead of just two to ensure we choose the
+ // correct orientation if there's a reflection.
+ if (isCTMPreservingAxisAlignedRectangles &&
+ ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
+ ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
+ ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
+ if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
+ snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
+ // Nothing to draw; avoid scaling by zero and other weirdness that
+ // could put the context in an error state.
+ continue;
+ }
+ // Set the context's transform to the transform that maps fillRect to
+ // snappedFillRect. The part of the gradient that was going to
+ // exactly fill fillRect will fill snappedFillRect instead.
+ gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
+ snappedFillRectTopLeft, snappedFillRectTopRight,
+ snappedFillRectBottomRight);
+ ctx->SetMatrix(transform);
+ }
+ ctx->NewPath();
+ ctx->Rectangle(fillRect);
+
+ gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
+ gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
+ Color edgeColor;
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
+ RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
+ gradientStart, gradientEnd, &edgeColor)) {
+ ctx->SetColor(edgeColor);
+ } else {
+ ctx->SetMatrix(
+ ctx->CurrentMatrix().Copy().Translate(tileRect.TopLeft()));
+ ctx->SetPattern(gradientPattern);
+ }
+ ctx->Fill();
+ ctx->SetMatrix(ctm);
+ }
+ }
+}
+
+static CompositionOp
+DetermineCompositionOp(const nsCSSRendering::PaintBGParams& aParams,
+ const nsStyleImageLayers& aLayers,
+ uint32_t aLayerIndex)
+{
+ if (aParams.layer >= 0) {
+ // When drawing a single layer, use the specified composition op.
+ return aParams.compositionOp;
+ }
+
+ const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
+ // When drawing all layers, get the compositon op from each image layer.
+ if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
+ // Always using OP_OVER mode while drawing the bottom mask layer.
+ if (aLayerIndex == (aLayers.mImageCount - 1)) {
+ return CompositionOp::OP_OVER;
+ }
+
+ return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
+ }
+
+ return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
+}
+
+DrawResult
+nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams,
+ nsStyleContext *aBackgroundSC,
+ const nsStyleBorder& aBorder)
+{
+ NS_PRECONDITION(aParams.frame,
+ "Frame is expected to be provided to PaintBackground");
+
+ // If we're drawing all layers, aCompositonOp is ignored, so make sure that
+ // it was left at its default value.
+ MOZ_ASSERT_IF(aParams.layer == -1,
+ aParams.compositionOp == CompositionOp::OP_OVER);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Check to see if we have an appearance defined. If so, we let the theme
+ // renderer draw the background and bail out.
+ // XXXzw this ignores aParams.bgClipRect.
+ const nsStyleDisplay* displayData = aParams.frame->StyleDisplay();
+ if (displayData->mAppearance) {
+ nsITheme *theme = aParams.presCtx.GetTheme();
+ if (theme && theme->ThemeSupportsWidget(&aParams.presCtx,
+ aParams.frame,
+ displayData->mAppearance)) {
+ nsRect drawing(aParams.borderArea);
+ theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(),
+ aParams.frame, displayData->mAppearance,
+ &drawing);
+ drawing.IntersectRect(drawing, aParams.dirtyRect);
+ theme->DrawWidgetBackground(&aParams.renderingCtx, aParams.frame,
+ displayData->mAppearance, aParams.borderArea,
+ drawing);
+ return DrawResult::SUCCESS;
+ }
+ }
+
+ // For canvas frames (in the CSS sense) we draw the background color using
+ // a solid color item that gets added in nsLayoutUtils::PaintFrame,
+ // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
+ // color may be moved into nsDisplayCanvasBackground by
+ // nsPresShell::AddCanvasBackgroundColorItem, and painted by
+ // nsDisplayCanvasBackground directly.) Either way we don't need to
+ // paint the background color here.
+ bool isCanvasFrame = IsCanvasFrame(aParams.frame);
+
+ // Determine whether we are drawing background images and/or
+ // background colors.
+ bool drawBackgroundImage;
+ bool drawBackgroundColor;
+
+ nscolor bgColor = DetermineBackgroundColor(&aParams.presCtx,
+ aBackgroundSC,
+ aParams.frame,
+ drawBackgroundImage,
+ drawBackgroundColor);
+
+ bool paintMask = (aParams.paintFlags & PAINTBG_MASK_IMAGE);
+ const nsStyleImageLayers& layers = paintMask ?
+ aBackgroundSC->StyleSVGReset()->mMask :
+ aBackgroundSC->StyleBackground()->mImage;
+ // If we're drawing a specific layer, we don't want to draw the
+ // background color.
+ if ((drawBackgroundColor && aParams.layer >= 0) || paintMask) {
+ drawBackgroundColor = false;
+ }
+
+ // At this point, drawBackgroundImage and drawBackgroundColor are
+ // true if and only if we are actually supposed to paint an image or
+ // color into aDirtyRect, respectively.
+ if (!drawBackgroundImage && !drawBackgroundColor)
+ return DrawResult::SUCCESS;
+
+ // Compute the outermost boundary of the area that might be painted.
+ // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
+ Sides skipSides = aParams.frame->GetSkipSides();
+ nsRect paintBorderArea =
+ ::BoxDecorationRectForBackground(aParams.frame, aParams.borderArea,
+ skipSides, &aBorder);
+ nsRect clipBorderArea =
+ ::BoxDecorationRectForBorder(aParams.frame, aParams.borderArea,
+ skipSides, &aBorder);
+
+ // The 'bgClipArea' (used only by the image tiling logic, far below)
+ // is the caller-provided aParams.bgClipRect if any, or else the area
+ // determined by the value of 'background-clip' in
+ // SetupCurrentBackgroundClip. (Arguably it should be the
+ // intersection, but that breaks the table painter -- in particular,
+ // taking the intersection breaks reftests/bugs/403249-1[ab].)
+ gfxContext* ctx = aParams.renderingCtx.ThebesContext();
+ nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
+ ImageLayerClipState clipState;
+ if (aParams.bgClipRect) {
+ clipState.mBGClipArea = *aParams.bgClipRect;
+ clipState.mCustomClip = true;
+ clipState.mHasRoundedCorners = false;
+ SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
+ &clipState.mDirtyRect, &clipState.mDirtyRectGfx);
+ } else {
+ GetImageLayerClip(layers.BottomLayer(),
+ aParams.frame, aBorder, aParams.borderArea,
+ aParams.dirtyRect,
+ (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
+ appUnitsPerPixel,
+ &clipState);
+ }
+
+ // If we might be using a background color, go ahead and set it now.
+ if (drawBackgroundColor && !isCanvasFrame)
+ ctx->SetColor(Color::FromABGR(bgColor));
+
+ // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
+ // in the cases we need it.
+ gfxContextAutoSaveRestore autoSR;
+
+ // If there is no background image, draw a color. (If there is
+ // neither a background image nor a color, we wouldn't have gotten
+ // this far.)
+ if (!drawBackgroundImage) {
+ if (!isCanvasFrame) {
+ DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
+ }
+ return DrawResult::SUCCESS;
+ }
+
+ if (layers.mImageCount < 1) {
+ // Return if there are no background layers, all work from this point
+ // onwards happens iteratively on these.
+ return DrawResult::SUCCESS;
+ }
+
+ // Validate the layer range before we start iterating.
+ int32_t startLayer = aParams.layer;
+ int32_t nLayers = 1;
+ if (startLayer < 0) {
+ startLayer = (int32_t)layers.mImageCount - 1;
+ nLayers = layers.mImageCount;
+ }
+
+ // Ensure we get invalidated for loads of the image. We need to do
+ // this here because this might be the only code that knows about the
+ // association of the style data with the frame.
+ if (aBackgroundSC != aParams.frame->StyleContext()) {
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, startLayer, nLayers) {
+ aParams.frame->AssociateImage(layers.mLayers[i].mImage,
+ &aParams.presCtx);
+ }
+ }
+
+ // The background color is rendered over the entire dirty area,
+ // even if the image isn't.
+ if (drawBackgroundColor && !isCanvasFrame) {
+ DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
+ }
+
+ if (drawBackgroundImage) {
+ bool clipSet = false;
+ uint8_t currentBackgroundClip = NS_STYLE_IMAGELAYER_CLIP_BORDER;
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, layers.mImageCount - 1,
+ nLayers + (layers.mImageCount -
+ startLayer - 1)) {
+ const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
+ if (!aParams.bgClipRect) {
+ if (currentBackgroundClip != layer.mClip || !clipSet) {
+ currentBackgroundClip = layer.mClip;
+ // If clipSet is false that means this is the bottom layer and we
+ // already called GetImageLayerClip above and it stored its results
+ // in clipState.
+ if (clipSet) {
+ autoSR.Restore(); // reset the previous one
+ GetImageLayerClip(layer, aParams.frame,
+ aBorder, aParams.borderArea, aParams.dirtyRect,
+ (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
+ appUnitsPerPixel, &clipState);
+ }
+ SetupImageLayerClip(clipState, ctx, appUnitsPerPixel, &autoSR);
+ clipSet = true;
+ if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
+ // We're drawing the background for the joined continuation boxes
+ // so we need to clip that to the slice that we want for this
+ // frame.
+ gfxRect clip =
+ nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
+ autoSR.EnsureSaved(ctx);
+ ctx->NewPath();
+ ctx->SnappedRectangle(clip);
+ ctx->Clip();
+ }
+ }
+ }
+ if ((aParams.layer < 0 || i == (uint32_t)startLayer) &&
+ !clipState.mDirtyRectGfx.IsEmpty()) {
+ CompositionOp co = DetermineCompositionOp(aParams, layers, i);
+ nsBackgroundLayerState state =
+ PrepareImageLayer(&aParams.presCtx, aParams.frame,
+ aParams.paintFlags, paintBorderArea, clipState.mBGClipArea,
+ layer, nullptr);
+ result &= state.mImageRenderer.PrepareResult();
+ if (!state.mFillArea.IsEmpty()) {
+ if (co != CompositionOp::OP_OVER) {
+ NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
+ "It is assumed the initial op is OP_OVER, when it is "
+ "restored later");
+ ctx->SetOp(co);
+ }
+
+ result &=
+ state.mImageRenderer.DrawBackground(&aParams.presCtx,
+ aParams.renderingCtx,
+ state.mDestArea, state.mFillArea,
+ state.mAnchor + paintBorderArea.TopLeft(),
+ clipState.mDirtyRect,
+ state.mRepeatSize);
+
+ if (co != CompositionOp::OP_OVER) {
+ ctx->SetOp(CompositionOp::OP_OVER);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+nsRect
+nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame** aAttachedToFrame,
+ bool* aOutIsTransformedFixed)
+{
+ // Compute background origin area relative to aBorderArea now as we may need
+ // it to compute the effective image size for a CSS gradient.
+ nsRect bgPositioningArea;
+
+ nsIAtom* frameType = aForFrame->GetType();
+ nsIFrame* geometryFrame = aForFrame;
+ if (MOZ_UNLIKELY(frameType == nsGkAtoms::scrollFrame &&
+ NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
+ bgPositioningArea = nsRect(
+ scrollableFrame->GetScrolledFrame()->GetPosition()
+ // For the dir=rtl case:
+ + scrollableFrame->GetScrollRange().TopLeft(),
+ scrollableFrame->GetScrolledRect().Size());
+ // The ScrolledRect’s size does not include the borders or scrollbars,
+ // reverse the handling of background-origin
+ // compared to the common case below.
+ if (aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_BORDER) {
+ nsMargin border = geometryFrame->GetUsedBorder();
+ border.ApplySkipSides(geometryFrame->GetSkipSides());
+ bgPositioningArea.Inflate(border);
+ bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
+ } else if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
+ nsMargin padding = geometryFrame->GetUsedPadding();
+ padding.ApplySkipSides(geometryFrame->GetSkipSides());
+ bgPositioningArea.Deflate(padding);
+ NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
+ "unknown background-origin value");
+ }
+ *aAttachedToFrame = aForFrame;
+ return bgPositioningArea;
+ }
+
+ if (MOZ_UNLIKELY(frameType == nsGkAtoms::canvasFrame)) {
+ geometryFrame = aForFrame->PrincipalChildList().FirstChild();
+ // geometryFrame might be null if this canvas is a page created
+ // as an overflow container (e.g. the in-flow content has already
+ // finished and this page only displays the continuations of
+ // absolutely positioned content).
+ if (geometryFrame) {
+ bgPositioningArea = geometryFrame->GetRect();
+ }
+ } else {
+ bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
+ }
+
+ // Background images are tiled over the 'background-clip' area
+ // but the origin of the tiling is based on the 'background-origin' area
+ // XXX: Bug 1303623 will bring in new origin value, we should iterate from
+ // NS_STYLE_IMAGELAYER_ORIGIN_MARGIN instead of
+ // NS_STYLE_IMAGELAYER_ORIGIN_BORDER.
+ if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_BORDER && geometryFrame) {
+ nsMargin border = geometryFrame->GetUsedBorder();
+ if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
+ border += geometryFrame->GetUsedPadding();
+ NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
+ "unknown background-origin value");
+ }
+ bgPositioningArea.Deflate(border);
+ }
+
+ nsIFrame* attachedToFrame = aForFrame;
+ if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment) {
+ // If it's a fixed background attachment, then the image is placed
+ // relative to the viewport, which is the area of the root frame
+ // in a screen context or the page content frame in a print context.
+ attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
+ NS_ASSERTION(attachedToFrame, "no root frame");
+ nsIFrame* pageContentFrame = nullptr;
+ if (aPresContext->IsPaginated()) {
+ pageContentFrame =
+ nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
+ if (pageContentFrame) {
+ attachedToFrame = pageContentFrame;
+ }
+ // else this is an embedded shell and its root frame is what we want
+ }
+
+ // If the background is affected by a transform, treat is as if it
+ // wasn't fixed.
+ if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
+ attachedToFrame = aForFrame;
+ *aOutIsTransformedFixed = true;
+ } else {
+ // Set the background positioning area to the viewport's area
+ // (relative to aForFrame)
+ bgPositioningArea =
+ nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
+
+ if (!pageContentFrame) {
+ // Subtract the size of scrollbars.
+ nsIScrollableFrame* scrollableFrame =
+ aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
+ if (scrollableFrame) {
+ nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
+ bgPositioningArea.Deflate(scrollbars);
+ }
+ }
+ }
+ }
+ *aAttachedToFrame = attachedToFrame;
+
+ return bgPositioningArea;
+}
+
+// Implementation of the formula for computation of background-repeat round
+// See http://dev.w3.org/csswg/css3-background/#the-background-size
+// This function returns the adjusted size of the background image.
+static nscoord
+ComputeRoundedSize(nscoord aCurrentSize, nscoord aPositioningSize)
+{
+ float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
+ if (repeatCount < 1.0f) {
+ return aPositioningSize;
+ }
+ return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
+}
+
+// Apply the CSS image sizing algorithm as it applies to background images.
+// See http://www.w3.org/TR/css3-background/#the-background-size .
+// aIntrinsicSize is the size that the background image 'would like to be'.
+// It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
+static nsSize
+ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aBgPositioningArea,
+ const nsStyleImageLayers::Size& aLayerSize,
+ uint8_t aXRepeat, uint8_t aYRepeat)
+{
+ nsSize imageSize;
+
+ // Size is dictated by cover or contain rules.
+ if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain ||
+ aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) {
+ nsImageRenderer::FitType fitType =
+ aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover
+ ? nsImageRenderer::COVER
+ : nsImageRenderer::CONTAIN;
+ imageSize = nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
+ aIntrinsicSize.mRatio,
+ fitType);
+ } else {
+ // No cover/contain constraint, use default algorithm.
+ CSSSizeOrRatio specifiedSize;
+ if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) {
+ specifiedSize.SetWidth(
+ aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
+ }
+ if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) {
+ specifiedSize.SetHeight(
+ aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
+ }
+
+ imageSize = nsImageRenderer::ComputeConcreteSize(specifiedSize,
+ aIntrinsicSize,
+ aBgPositioningArea);
+ }
+
+ // See https://www.w3.org/TR/css3-background/#background-size .
+ // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second
+ // step. The UA must scale the image in that dimension (or both dimensions) so that
+ // it fits a whole number of times in the background positioning area."
+ // "If 'background-repeat' is 'round' for one dimension only and if 'background-size'
+ // is 'auto' for the other dimension, then there is a third step: that other dimension
+ // is scaled so that the original aspect ratio is restored."
+ bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND &&
+ aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+
+ // Calculate the rounded size only if the background-size computation
+ // returned a correct size for the image.
+ if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+ imageSize.width = ComputeRoundedSize(imageSize.width, aBgPositioningArea.width);
+ if (!isRepeatRoundInBothDimensions &&
+ aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) {
+ // Restore intrinsic rato
+ if (aIntrinsicSize.mRatio.width) {
+ float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
+ imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
+ }
+ }
+ }
+
+ // Calculate the rounded size only if the background-size computation
+ // returned a correct size for the image.
+ if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+ imageSize.height = ComputeRoundedSize(imageSize.height, aBgPositioningArea.height);
+ if (!isRepeatRoundInBothDimensions &&
+ aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) {
+ // Restore intrinsic rato
+ if (aIntrinsicSize.mRatio.height) {
+ float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
+ imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale);
+ }
+ }
+ }
+
+ return imageSize;
+}
+
+/* ComputeSpacedRepeatSize
+ * aImageDimension: the image width/height
+ * aAvailableSpace: the background positioning area width/height
+ * aRepeat: determine whether the image is repeated
+ * Returns the image size plus gap size of app units for use as spacing
+ */
+static nscoord
+ComputeSpacedRepeatSize(nscoord aImageDimension,
+ nscoord aAvailableSpace,
+ bool& aRepeat) {
+ float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
+
+ if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
+ aRepeat = false;
+ return aImageDimension;
+ } else {
+ aRepeat = true;
+ return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
+ }
+}
+
+/* ComputeBorderSpacedRepeatSize
+ * aImageDimension: the image width/height
+ * aAvailableSpace: the background positioning area width/height
+ * aSpace: the space between each image
+ * Returns the image size plus gap size of app units for use as spacing
+ */
+static nscoord
+ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
+ nscoord aAvailableSpace,
+ nscoord& aSpace)
+{
+ int32_t count = aAvailableSpace / aImageDimension;
+ aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
+ return aSpace + aImageDimension;
+}
+
+nsBackgroundLayerState
+nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ uint32_t aFlags,
+ const nsRect& aBorderArea,
+ const nsRect& aBGClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ bool* aOutIsTransformedFixed)
+{
+ /*
+ * The properties we need to keep in mind when drawing style image
+ * layers are:
+ *
+ * background-image/ mask-image
+ * background-repeat/ mask-repeat
+ * background-attachment
+ * background-position/ mask-position
+ * background-clip/ mask-clip
+ * background-origin/ mask-origin
+ * background-size/ mask-size
+ * background-blend-mode
+ * box-decoration-break
+ * mask-mode
+ * mask-composite
+ *
+ * (background-color applies to the entire element and not to individual
+ * layers, so it is irrelevant to this method.)
+ *
+ * These properties have the following dependencies upon each other when
+ * determining rendering:
+ *
+ * background-image/ mask-image
+ * no dependencies
+ * background-repeat/ mask-repeat
+ * no dependencies
+ * background-attachment
+ * no dependencies
+ * background-position/ mask-position
+ * depends upon background-size/mask-size (for the image's scaled size)
+ * and background-break (for the background positioning area)
+ * background-clip/ mask-clip
+ * no dependencies
+ * background-origin/ mask-origin
+ * depends upon background-attachment (only in the case where that value
+ * is 'fixed')
+ * background-size/ mask-size
+ * depends upon box-decoration-break (for the background positioning area
+ * for resolving percentages), background-image (for the image's intrinsic
+ * size), background-repeat (if that value is 'round'), and
+ * background-origin (for the background painting area, when
+ * background-repeat is 'round')
+ * background-blend-mode
+ * no dependencies
+ * mask-mode
+ * no dependencies
+ * mask-composite
+ * no dependencies
+ * box-decoration-break
+ * no dependencies
+ *
+ * As a result of only-if dependencies we don't strictly do a topological
+ * sort of the above properties when processing, but it's pretty close to one:
+ *
+ * background-clip/mask-clip (by caller)
+ * background-image/ mask-image
+ * box-decoration-break, background-origin/ mask origin
+ * background-attachment (postfix for background-origin if 'fixed')
+ * background-size/ mask-size
+ * background-position/ mask-position
+ * background-repeat/ mask-repeat
+ */
+
+ uint32_t irFlags = 0;
+ if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
+ irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+ if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
+ irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
+ }
+
+ nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
+ if (!state.mImageRenderer.PrepareImage()) {
+ // There's no image or it's not ready to be painted.
+ if (aOutIsTransformedFixed) {
+ *aOutIsTransformedFixed = false;
+ }
+ return state;
+ }
+
+ // The frame to which the background is attached
+ nsIFrame* attachedToFrame = aForFrame;
+ // Is the background marked 'fixed', but affected by a transform?
+ bool transformedFixed = false;
+ // Compute background origin area relative to aBorderArea now as we may need
+ // it to compute the effective image size for a CSS gradient.
+ nsRect bgPositioningArea =
+ ComputeImageLayerPositioningArea(aPresContext, aForFrame, aBorderArea,
+ aLayer, &attachedToFrame, &transformedFixed);
+ if (aOutIsTransformedFixed) {
+ *aOutIsTransformedFixed = transformedFixed;
+ }
+
+ // For background-attachment:fixed backgrounds, we'll limit the area
+ // where the background can be drawn to the viewport.
+ nsRect bgClipRect = aBGClipRect;
+
+ // Compute the anchor point.
+ //
+ // relative to aBorderArea.TopLeft() (which is where the top-left
+ // of aForFrame's border-box will be rendered)
+ nsPoint imageTopLeft;
+ if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment && !transformedFixed) {
+ if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
+ // Clip background-attachment:fixed backgrounds to the viewport, if we're
+ // painting to the screen and not transformed. This avoids triggering
+ // tiling in common cases, without affecting output since drawing is
+ // always clipped to the viewport when we draw to the screen. (But it's
+ // not a pure optimization since it can affect the values of pixels at the
+ // edge of the viewport --- whether they're sampled from a putative "next
+ // tile" or not.)
+ bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
+ }
+ }
+
+ int repeatX = aLayer.mRepeat.mXRepeat;
+ int repeatY = aLayer.mRepeat.mYRepeat;
+
+ // Scale the image as specified for background-size and background-repeat.
+ // Also as required for proper background positioning when background-position
+ // is defined with percentages.
+ CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
+ nsSize bgPositionSize = bgPositioningArea.Size();
+ nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
+ bgPositionSize,
+ aLayer.mSize,
+ repeatX,
+ repeatY);
+
+ if (imageSize.width <= 0 || imageSize.height <= 0)
+ return state;
+
+ state.mImageRenderer.SetPreferredSize(intrinsicSize,
+ imageSize);
+
+ // Compute the position of the background now that the background's size is
+ // determined.
+ nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
+ bgPositionSize, imageSize,
+ &imageTopLeft, &state.mAnchor);
+ state.mRepeatSize = imageSize;
+ if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+ bool isRepeat;
+ state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width,
+ bgPositionSize.width,
+ isRepeat);
+ if (isRepeat) {
+ imageTopLeft.x = 0;
+ state.mAnchor.x = 0;
+ } else {
+ repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+ }
+ }
+
+ if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+ bool isRepeat;
+ state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height,
+ bgPositionSize.height,
+ isRepeat);
+ if (isRepeat) {
+ imageTopLeft.y = 0;
+ state.mAnchor.y = 0;
+ } else {
+ repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+ }
+ }
+
+ imageTopLeft += bgPositioningArea.TopLeft();
+ state.mAnchor += bgPositioningArea.TopLeft();
+ state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
+ state.mFillArea = state.mDestArea;
+
+ ExtendMode repeatMode = ExtendMode::CLAMP;
+ if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
+ repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+ state.mFillArea.x = bgClipRect.x;
+ state.mFillArea.width = bgClipRect.width;
+ repeatMode = ExtendMode::REPEAT_X;
+ }
+ if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
+ repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+ state.mFillArea.y = bgClipRect.y;
+ state.mFillArea.height = bgClipRect.height;
+
+ /***
+ * We're repeating on the X axis already,
+ * so if we have to repeat in the Y axis,
+ * we really need to repeat in both directions.
+ */
+ if (repeatMode == ExtendMode::REPEAT_X) {
+ repeatMode = ExtendMode::REPEAT;
+ } else {
+ repeatMode = ExtendMode::REPEAT_Y;
+ }
+ }
+ state.mImageRenderer.SetExtendMode(repeatMode);
+ state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
+
+ state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
+
+ return state;
+}
+
+nsRect
+nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsRect& aClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ uint32_t aFlags)
+{
+ Sides skipSides = aForFrame->GetSkipSides();
+ nsRect borderArea =
+ ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
+ nsBackgroundLayerState state =
+ PrepareImageLayer(aPresContext, aForFrame, aFlags, borderArea,
+ aClipRect, aLayer);
+ return state.mFillArea;
+}
+
+static DrawResult
+DrawBorderImage(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsStyleBorder& aStyleBorder,
+ const nsRect& aDirtyRect,
+ Sides aSkipSides,
+ PaintBorderFlags aFlags)
+{
+ NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
+ "drawing border image that isn't successfully loaded");
+
+ if (aDirtyRect.IsEmpty()) {
+ return DrawResult::SUCCESS;
+ }
+
+ uint32_t irFlags = 0;
+ if (aFlags & PaintBorderFlags::SYNC_DECODE_IMAGES) {
+ irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+ }
+ nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, irFlags);
+
+ // Ensure we get invalidated for loads and animations of the image.
+ // We need to do this here because this might be the only code that
+ // knows about the association of the style data with the frame.
+ // XXX We shouldn't really... since if anybody is passing in a
+ // different style, they'll potentially have the wrong size for the
+ // border too.
+ aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
+
+ if (!renderer.PrepareImage()) {
+ return renderer.PrepareResult();
+ }
+
+ // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
+ // in case we need it.
+ gfxContextAutoSaveRestore autoSR;
+
+ // Determine the border image area, which by default corresponds to the
+ // border box but can be modified by 'border-image-outset'.
+ // Note that 'border-radius' do not apply to 'border-image' borders per
+ // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
+ nsRect borderImgArea;
+ nsMargin borderWidths(aStyleBorder.GetComputedBorder());
+ nsMargin imageOutset(aStyleBorder.GetImageOutset());
+ if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
+ borderImgArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
+ aSkipSides, &aStyleBorder);
+ if (borderImgArea.IsEqualEdges(aBorderArea)) {
+ // No need for a clip, just skip the sides we don't want.
+ borderWidths.ApplySkipSides(aSkipSides);
+ imageOutset.ApplySkipSides(aSkipSides);
+ borderImgArea.Inflate(imageOutset);
+ } else {
+ // We're drawing borders around the joined continuation boxes so we need
+ // to clip that to the slice that we want for this frame.
+ borderImgArea.Inflate(imageOutset);
+ imageOutset.ApplySkipSides(aSkipSides);
+ nsRect clip = aBorderArea;
+ clip.Inflate(imageOutset);
+ autoSR.EnsureSaved(aRenderingContext.ThebesContext());
+ aRenderingContext.ThebesContext()->
+ Clip(NSRectToSnappedRect(clip,
+ aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ *aRenderingContext.GetDrawTarget()));
+ }
+ } else {
+ borderImgArea = aBorderArea;
+ borderImgArea.Inflate(imageOutset);
+ }
+
+ // Calculate the image size used to compute slice points.
+ CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
+ nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
+ intrinsicSize,
+ borderImgArea.Size());
+ renderer.SetPreferredSize(intrinsicSize, imageSize);
+
+ // Compute the used values of 'border-image-slice' and 'border-image-width';
+ // we do them together because the latter can depend on the former.
+ nsMargin slice;
+ nsMargin border;
+ NS_FOR_CSS_SIDES(s) {
+ nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
+ int32_t imgDimension = NS_SIDE_IS_VERTICAL(s)
+ ? imageSize.width : imageSize.height;
+ nscoord borderDimension = NS_SIDE_IS_VERTICAL(s)
+ ? borderImgArea.width : borderImgArea.height;
+ double value;
+ switch (coord.GetUnit()) {
+ case eStyleUnit_Percent:
+ value = coord.GetPercentValue() * imgDimension;
+ break;
+ case eStyleUnit_Factor:
+ value = nsPresContext::CSSPixelsToAppUnits(
+ NS_lround(coord.GetFactorValue()));
+ break;
+ default:
+ NS_NOTREACHED("unexpected CSS unit for image slice");
+ value = 0;
+ break;
+ }
+ if (value < 0)
+ value = 0;
+ if (value > imgDimension)
+ value = imgDimension;
+ slice.Side(s) = value;
+
+ coord = aStyleBorder.mBorderImageWidth.Get(s);
+ switch (coord.GetUnit()) {
+ case eStyleUnit_Coord: // absolute dimension
+ value = coord.GetCoordValue();
+ break;
+ case eStyleUnit_Percent:
+ value = coord.GetPercentValue() * borderDimension;
+ break;
+ case eStyleUnit_Factor:
+ value = coord.GetFactorValue() * borderWidths.Side(s);
+ break;
+ case eStyleUnit_Auto: // same as the slice value, in CSS pixels
+ value = slice.Side(s);
+ break;
+ default:
+ NS_NOTREACHED("unexpected CSS unit for border image area division");
+ value = 0;
+ break;
+ }
+ // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
+ // because we expect value to be non-negative.
+ MOZ_ASSERT(value >= 0);
+ border.Side(s) = NSToCoordRoundWithClamp(value);
+ MOZ_ASSERT(border.Side(s) >= 0);
+ }
+
+ // "If two opposite border-image-width offsets are large enough that they
+ // overlap, their used values are proportionately reduced until they no
+ // longer overlap."
+ uint32_t combinedBorderWidth = uint32_t(border.left) +
+ uint32_t(border.right);
+ double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
+ ? borderImgArea.width / double(combinedBorderWidth)
+ : 1.0;
+ uint32_t combinedBorderHeight = uint32_t(border.top) +
+ uint32_t(border.bottom);
+ double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
+ ? borderImgArea.height / double(combinedBorderHeight)
+ : 1.0;
+ double scale = std::min(scaleX, scaleY);
+ if (scale < 1.0) {
+ border.left *= scale;
+ border.right *= scale;
+ border.top *= scale;
+ border.bottom *= scale;
+ NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
+ border.top + border.bottom <= borderImgArea.height,
+ "rounding error in width reduction???");
+ }
+
+ // These helper tables recharacterize the 'slice' and 'width' margins
+ // in a more convenient form: they are the x/y/width/height coords
+ // required for various bands of the border, and they have been transformed
+ // to be relative to the innerRect (for 'slice') or the page (for 'border').
+ enum {
+ LEFT, MIDDLE, RIGHT,
+ TOP = LEFT, BOTTOM = RIGHT
+ };
+ const nscoord borderX[3] = {
+ borderImgArea.x + 0,
+ borderImgArea.x + border.left,
+ borderImgArea.x + borderImgArea.width - border.right,
+ };
+ const nscoord borderY[3] = {
+ borderImgArea.y + 0,
+ borderImgArea.y + border.top,
+ borderImgArea.y + borderImgArea.height - border.bottom,
+ };
+ const nscoord borderWidth[3] = {
+ border.left,
+ borderImgArea.width - border.left - border.right,
+ border.right,
+ };
+ const nscoord borderHeight[3] = {
+ border.top,
+ borderImgArea.height - border.top - border.bottom,
+ border.bottom,
+ };
+ const int32_t sliceX[3] = {
+ 0,
+ slice.left,
+ imageSize.width - slice.right,
+ };
+ const int32_t sliceY[3] = {
+ 0,
+ slice.top,
+ imageSize.height - slice.bottom,
+ };
+ const int32_t sliceWidth[3] = {
+ slice.left,
+ std::max(imageSize.width - slice.left - slice.right, 0),
+ slice.right,
+ };
+ const int32_t sliceHeight[3] = {
+ slice.top,
+ std::max(imageSize.height - slice.top - slice.bottom, 0),
+ slice.bottom,
+ };
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // intrinsicSize.CanComputeConcreteSize() return false means we can not
+ // read intrinsic size from aStyleBorder.mBorderImageSource.
+ // In this condition, we pass imageSize(a resolved size comes from
+ // default sizing algorithm) to renderer as the viewport size.
+ Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
+ Nothing() : Some(imageSize);
+ bool hasIntrinsicRatio = intrinsicSize.HasRatio();
+ renderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
+
+ for (int i = LEFT; i <= RIGHT; i++) {
+ for (int j = TOP; j <= BOTTOM; j++) {
+ uint8_t fillStyleH, fillStyleV;
+ nsSize unitSize;
+
+ if (i == MIDDLE && j == MIDDLE) {
+ // Discard the middle portion unless set to fill.
+ if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
+ aStyleBorder.mBorderImageFill) {
+ continue;
+ }
+
+ NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
+ aStyleBorder.mBorderImageFill,
+ "Unexpected border image fill");
+
+ // css-background:
+ // The middle image's width is scaled by the same factor as the
+ // top image unless that factor is zero or infinity, in which
+ // case the scaling factor of the bottom is substituted, and
+ // failing that, the width is not scaled. The height of the
+ // middle image is scaled by the same factor as the left image
+ // unless that factor is zero or infinity, in which case the
+ // scaling factor of the right image is substituted, and failing
+ // that, the height is not scaled.
+ gfxFloat hFactor, vFactor;
+
+ if (0 < border.left && 0 < slice.left)
+ vFactor = gfxFloat(border.left)/slice.left;
+ else if (0 < border.right && 0 < slice.right)
+ vFactor = gfxFloat(border.right)/slice.right;
+ else
+ vFactor = 1;
+
+ if (0 < border.top && 0 < slice.top)
+ hFactor = gfxFloat(border.top)/slice.top;
+ else if (0 < border.bottom && 0 < slice.bottom)
+ hFactor = gfxFloat(border.bottom)/slice.bottom;
+ else
+ hFactor = 1;
+
+ unitSize.width = sliceWidth[i]*hFactor;
+ unitSize.height = sliceHeight[j]*vFactor;
+ fillStyleH = aStyleBorder.mBorderImageRepeatH;
+ fillStyleV = aStyleBorder.mBorderImageRepeatV;
+
+ } else if (i == MIDDLE) { // top, bottom
+ // Sides are always stretched to the thickness of their border,
+ // and stretched proportionately on the other axis.
+ gfxFloat factor;
+ if (0 < borderHeight[j] && 0 < sliceHeight[j])
+ factor = gfxFloat(borderHeight[j])/sliceHeight[j];
+ else
+ factor = 1;
+
+ unitSize.width = sliceWidth[i]*factor;
+ unitSize.height = borderHeight[j];
+ fillStyleH = aStyleBorder.mBorderImageRepeatH;
+ fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+
+ } else if (j == MIDDLE) { // left, right
+ gfxFloat factor;
+ if (0 < borderWidth[i] && 0 < sliceWidth[i])
+ factor = gfxFloat(borderWidth[i])/sliceWidth[i];
+ else
+ factor = 1;
+
+ unitSize.width = borderWidth[i];
+ unitSize.height = sliceHeight[j]*factor;
+ fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+ fillStyleV = aStyleBorder.mBorderImageRepeatV;
+
+ } else {
+ // Corners are always stretched to fit the corner.
+ unitSize.width = borderWidth[i];
+ unitSize.height = borderHeight[j];
+ fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+ fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+ }
+
+ nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
+ nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
+ if (subArea.IsEmpty())
+ continue;
+
+ nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
+ result &=
+ renderer.DrawBorderImageComponent(aPresContext,
+ aRenderingContext, aDirtyRect,
+ destArea, CSSIntRect(intSubArea.x,
+ intSubArea.y,
+ intSubArea.width,
+ intSubArea.height),
+ fillStyleH, fillStyleV,
+ unitSize, j * (RIGHT + 1) + i,
+ svgViewportSize, hasIntrinsicRatio);
+ }
+ }
+
+ return result;
+}
+
+// Begin table border-collapsing section
+// These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
+// At some point, all functions should be unified to include the additional functionality that these provide
+
+static nscoord
+RoundIntToPixel(nscoord aValue,
+ nscoord aTwipsPerPixel,
+ bool aRoundDown = false)
+{
+ if (aTwipsPerPixel <= 0)
+ // We must be rendering to a device that has a resolution greater than Twips!
+ // In that case, aValue is as accurate as it's going to get.
+ return aValue;
+
+ nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
+ nscoord extra = aValue % aTwipsPerPixel;
+ nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
+ return finalValue;
+}
+
+static nscoord
+RoundFloatToPixel(float aValue,
+ nscoord aTwipsPerPixel,
+ bool aRoundDown = false)
+{
+ return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
+}
+
+static void SetPoly(const Rect& aRect, Point* poly)
+{
+ poly[0].x = aRect.x;
+ poly[0].y = aRect.y;
+ poly[1].x = aRect.x + aRect.width;
+ poly[1].y = aRect.y;
+ poly[2].x = aRect.x + aRect.width;
+ poly[2].y = aRect.y + aRect.height;
+ poly[3].x = aRect.x;
+ poly[3].y = aRect.y + aRect.height;
+}
+
+static void
+DrawDashedSegment(DrawTarget& aDrawTarget,
+ nsRect aRect,
+ nscoord aDashLength,
+ nscolor aColor,
+ int32_t aAppUnitsPerDevPixel,
+ nscoord aTwipsPerPixel,
+ bool aHorizontal)
+{
+ ColorPattern color(ToDeviceColor(aColor));
+ DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
+ StrokeOptions strokeOptions;
+
+ Float dash[2];
+ dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
+ dash[1] = dash[0];
+
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+
+ if (aHorizontal) {
+ nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
+ nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
+ strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
+ StrokeLineWithSnapping(left, right,
+ aAppUnitsPerDevPixel, aDrawTarget,
+ color, strokeOptions, drawOptions);
+ } else {
+ nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
+ nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
+ strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
+ StrokeLineWithSnapping(top, bottom,
+ aAppUnitsPerDevPixel, aDrawTarget,
+ color, strokeOptions, drawOptions);
+ }
+}
+
+static void
+DrawSolidBorderSegment(DrawTarget& aDrawTarget,
+ nsRect aRect,
+ nscolor aColor,
+ int32_t aAppUnitsPerDevPixel,
+ nscoord aTwipsPerPixel,
+ uint8_t aStartBevelSide = 0,
+ nscoord aStartBevelOffset = 0,
+ uint8_t aEndBevelSide = 0,
+ nscoord aEndBevelOffset = 0)
+{
+ ColorPattern color(ToDeviceColor(aColor));
+ DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
+
+ // We don't need to bevel single pixel borders
+ if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
+ ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
+ // simple rectangle
+ aDrawTarget.FillRect(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel,
+ aDrawTarget),
+ color, drawOptions);
+ }
+ else {
+ // polygon with beveling
+ Point poly[4];
+ SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
+ poly);
+
+ Float startBevelOffset =
+ NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
+ switch(aStartBevelSide) {
+ case NS_SIDE_TOP:
+ poly[0].x += startBevelOffset;
+ break;
+ case NS_SIDE_BOTTOM:
+ poly[3].x += startBevelOffset;
+ break;
+ case NS_SIDE_RIGHT:
+ poly[1].y += startBevelOffset;
+ break;
+ case NS_SIDE_LEFT:
+ poly[0].y += startBevelOffset;
+ }
+
+ Float endBevelOffset =
+ NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
+ switch(aEndBevelSide) {
+ case NS_SIDE_TOP:
+ poly[1].x -= endBevelOffset;
+ break;
+ case NS_SIDE_BOTTOM:
+ poly[2].x -= endBevelOffset;
+ break;
+ case NS_SIDE_RIGHT:
+ poly[2].y -= endBevelOffset;
+ break;
+ case NS_SIDE_LEFT:
+ poly[3].y -= endBevelOffset;
+ }
+
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ builder->MoveTo(poly[0]);
+ builder->LineTo(poly[1]);
+ builder->LineTo(poly[2]);
+ builder->LineTo(poly[3]);
+ builder->Close();
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget.Fill(path, color, drawOptions);
+ }
+}
+
+static void
+GetDashInfo(nscoord aBorderLength,
+ nscoord aDashLength,
+ nscoord aTwipsPerPixel,
+ int32_t& aNumDashSpaces,
+ nscoord& aStartDashLength,
+ nscoord& aEndDashLength)
+{
+ aNumDashSpaces = 0;
+ if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
+ aStartDashLength = aBorderLength;
+ aEndDashLength = 0;
+ }
+ else {
+ aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down
+ nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
+ if (extra > 0) {
+ nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
+ aStartDashLength += half;
+ aEndDashLength += (extra - half);
+ }
+ }
+}
+
+void
+nsCSSRendering::DrawTableBorderSegment(DrawTarget& aDrawTarget,
+ uint8_t aBorderStyle,
+ nscolor aBorderColor,
+ const nsStyleBackground* aBGColor,
+ const nsRect& aBorder,
+ int32_t aAppUnitsPerDevPixel,
+ int32_t aAppUnitsPerCSSPixel,
+ uint8_t aStartBevelSide,
+ nscoord aStartBevelOffset,
+ uint8_t aEndBevelSide,
+ nscoord aEndBevelOffset)
+{
+ bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
+ nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
+ uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;
+
+ if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
+ (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
+ // no beveling for 1 pixel border, dash or dot
+ aStartBevelOffset = 0;
+ aEndBevelOffset = 0;
+ }
+
+ switch (aBorderStyle) {
+ case NS_STYLE_BORDER_STYLE_NONE:
+ case NS_STYLE_BORDER_STYLE_HIDDEN:
+ //NS_ASSERTION(false, "style of none or hidden");
+ break;
+ case NS_STYLE_BORDER_STYLE_DOTTED:
+ case NS_STYLE_BORDER_STYLE_DASHED:
+ {
+ nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
+ // make the dash length proportional to the border thickness
+ dashLength *= (horizontal) ? aBorder.height : aBorder.width;
+ // make the min dash length for the ends 1/2 the dash length
+ nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle)
+ ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
+ minDashLength = std::max(minDashLength, twipsPerPixel);
+ nscoord numDashSpaces = 0;
+ nscoord startDashLength = minDashLength;
+ nscoord endDashLength = minDashLength;
+ if (horizontal) {
+ GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces,
+ startDashLength, endDashLength);
+ nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel);
+
+ rect.x += startDashLength + dashLength;
+ rect.width = aBorder.width
+ - (startDashLength + endDashLength + dashLength);
+ DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
+
+ rect.x += rect.width;
+ rect.width = endDashLength;
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel);
+ }
+ else {
+ GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces,
+ startDashLength, endDashLength);
+ nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel);
+
+ rect.y += rect.height + dashLength;
+ rect.height = aBorder.height
+ - (startDashLength + endDashLength + dashLength);
+ DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
+
+ rect.y += rect.height;
+ rect.height = endDashLength;
+ DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel);
+ }
+ }
+ break;
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
+ MOZ_FALLTHROUGH;
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
+ (!horizontal && (twipsPerPixel >= aBorder.width))) {
+ // a one pixel border
+ DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, aStartBevelOffset,
+ aEndBevelSide, aEndBevelOffset);
+ }
+ else {
+ nscoord startBevel = (aStartBevelOffset > 0)
+ ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0;
+ nscoord endBevel = (aEndBevelOffset > 0)
+ ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0;
+ mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
+ // FIXME: In theory, this should use the visited-dependent
+ // background color, but I don't care.
+ nscolor bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
+ aBGColor->mBackgroundColor,
+ aBorderColor);
+ nsRect rect(aBorder);
+ nscoord half;
+ if (horizontal) { // top, bottom
+ half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
+ rect.height = half;
+ if (NS_SIDE_TOP == aStartBevelSide) {
+ rect.x += startBevel;
+ rect.width -= startBevel;
+ }
+ if (NS_SIDE_TOP == aEndBevelSide) {
+ rect.width -= endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+ else { // left, right
+ half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
+ rect.width = half;
+ if (NS_SIDE_LEFT == aStartBevelSide) {
+ rect.y += startBevel;
+ rect.height -= startBevel;
+ }
+ if (NS_SIDE_LEFT == aEndBevelSide) {
+ rect.height -= endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+
+ rect = aBorder;
+ ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
+ // FIXME: In theory, this should use the visited-dependent
+ // background color, but I don't care.
+ bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
+ aBGColor->mBackgroundColor, aBorderColor);
+ if (horizontal) {
+ rect.y = rect.y + half;
+ rect.height = aBorder.height - half;
+ if (NS_SIDE_BOTTOM == aStartBevelSide) {
+ rect.x += startBevel;
+ rect.width -= startBevel;
+ }
+ if (NS_SIDE_BOTTOM == aEndBevelSide) {
+ rect.width -= endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+ else {
+ rect.x = rect.x + half;
+ rect.width = aBorder.width - half;
+ if (NS_SIDE_RIGHT == aStartBevelSide) {
+ rect.y += aStartBevelOffset - startBevel;
+ rect.height -= startBevel;
+ }
+ if (NS_SIDE_RIGHT == aEndBevelSide) {
+ rect.height -= endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+ }
+ break;
+ case NS_STYLE_BORDER_STYLE_DOUBLE:
+ // We can only do "double" borders if the thickness of the border
+ // is more than 2px. Otherwise, we fall through to painting a
+ // solid border.
+ if ((aBorder.width > 2*twipsPerPixel || horizontal) &&
+ (aBorder.height > 2*twipsPerPixel || !horizontal)) {
+ nscoord startBevel = (aStartBevelOffset > 0)
+ ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
+ nscoord endBevel = (aEndBevelOffset > 0)
+ ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
+ if (horizontal) { // top, bottom
+ nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);
+
+ // draw the top line or rect
+ nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
+ if (NS_SIDE_TOP == aStartBevelSide) {
+ topRect.x += aStartBevelOffset - startBevel;
+ topRect.width -= aStartBevelOffset - startBevel;
+ }
+ if (NS_SIDE_TOP == aEndBevelSide) {
+ topRect.width -= aEndBevelOffset - endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, topRect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+
+ // draw the botom line or rect
+ nscoord heightOffset = aBorder.height - thirdHeight;
+ nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
+ if (NS_SIDE_BOTTOM == aStartBevelSide) {
+ bottomRect.x += aStartBevelOffset - startBevel;
+ bottomRect.width -= aStartBevelOffset - startBevel;
+ }
+ if (NS_SIDE_BOTTOM == aEndBevelSide) {
+ bottomRect.width -= aEndBevelOffset - endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, bottomRect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+ else { // left, right
+ nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);
+
+ nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
+ if (NS_SIDE_LEFT == aStartBevelSide) {
+ leftRect.y += aStartBevelOffset - startBevel;
+ leftRect.height -= aStartBevelOffset - startBevel;
+ }
+ if (NS_SIDE_LEFT == aEndBevelSide) {
+ leftRect.height -= aEndBevelOffset - endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, leftRect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+
+ nscoord widthOffset = aBorder.width - thirdWidth;
+ nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
+ if (NS_SIDE_RIGHT == aStartBevelSide) {
+ rightRect.y += aStartBevelOffset - startBevel;
+ rightRect.height -= aStartBevelOffset - startBevel;
+ }
+ if (NS_SIDE_RIGHT == aEndBevelSide) {
+ rightRect.height -= aEndBevelOffset - endBevel;
+ }
+ DrawSolidBorderSegment(aDrawTarget, rightRect, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel,
+ aStartBevelSide, startBevel, aEndBevelSide,
+ endBevel);
+ }
+ break;
+ }
+ // else fall through to solid
+ MOZ_FALLTHROUGH;
+ case NS_STYLE_BORDER_STYLE_SOLID:
+ DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
+ aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
+ aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
+ break;
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ case NS_STYLE_BORDER_STYLE_INSET:
+ NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge");
+ break;
+ case NS_STYLE_BORDER_STYLE_AUTO:
+ NS_ASSERTION(false, "Unexpected 'auto' table border");
+ break;
+ }
+}
+
+// End table border-collapsing section
+
+Rect
+nsCSSRendering::ExpandPaintingRectForDecorationLine(
+ nsIFrame* aFrame,
+ const uint8_t aStyle,
+ const Rect& aClippedRect,
+ const Float aICoordInFrame,
+ const Float aCycleLength,
+ bool aVertical)
+{
+ switch (aStyle) {
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
+ break;
+ default:
+ NS_ERROR("Invalid style was specified");
+ return aClippedRect;
+ }
+
+ nsBlockFrame* block = nullptr;
+ // Note that when we paint the decoration lines in relative positioned
+ // box, we should paint them like all of the boxes are positioned as static.
+ nscoord framePosInBlockAppUnits = 0;
+ for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ block = do_QueryFrame(f);
+ if (block) {
+ break;
+ }
+ framePosInBlockAppUnits += aVertical ?
+ f->GetNormalPosition().y : f->GetNormalPosition().x;
+ }
+
+ NS_ENSURE_TRUE(block, aClippedRect);
+
+ nsPresContext *pc = aFrame->PresContext();
+ Float framePosInBlock = Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
+ int32_t rectPosInBlock =
+ int32_t(NS_round(framePosInBlock + aICoordInFrame));
+ int32_t extraStartEdge =
+ rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
+ Rect rect(aClippedRect);
+ if (aVertical) {
+ rect.y -= extraStartEdge;
+ rect.height += extraStartEdge;
+ } else {
+ rect.x -= extraStartEdge;
+ rect.width += extraStartEdge;
+ }
+ return rect;
+}
+
+void
+nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams)
+{
+ NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
+ "aStyle is none");
+
+ Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
+ if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
+ return;
+ }
+
+ if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
+ aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
+ aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
+ NS_ERROR("Invalid decoration value!");
+ return;
+ }
+
+ Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
+
+ ColorPattern color(ToDeviceColor(aParams.color));
+ StrokeOptions strokeOptions(lineThickness);
+ DrawOptions drawOptions;
+
+ Float dash[2];
+
+ AutoPopClips autoPopClips(&aDrawTarget);
+
+ switch (aParams.style) {
+ case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
+ break;
+ case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
+ autoPopClips.PushClipRect(rect);
+ Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
+ dash[0] = dashWidth;
+ dash[1] = dashWidth;
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ strokeOptions.mLineCap = CapStyle::BUTT;
+ rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
+ rect, aParams.icoordInFrame,
+ dashWidth * 2,
+ aParams.vertical);
+ // We should continue to draw the last dash even if it is not in the rect.
+ rect.width += dashWidth;
+ break;
+ }
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
+ autoPopClips.PushClipRect(rect);
+ Float dashWidth = lineThickness * DOT_LENGTH;
+ if (lineThickness > 2.0) {
+ dash[0] = 0.f;
+ dash[1] = dashWidth * 2.f;
+ strokeOptions.mLineCap = CapStyle::ROUND;
+ } else {
+ dash[0] = dashWidth;
+ dash[1] = dashWidth;
+ }
+ strokeOptions.mDashPattern = dash;
+ strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
+ rect, aParams.icoordInFrame,
+ dashWidth * 2,
+ aParams.vertical);
+ // We should continue to draw the last dot even if it is not in the rect.
+ rect.width += dashWidth;
+ break;
+ }
+ case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
+ autoPopClips.PushClipRect(rect);
+ if (lineThickness > 2.0) {
+ drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
+ } else {
+ // Don't use anti-aliasing here. Because looks like lighter color wavy
+ // line at this case. And probably, users don't think the
+ // non-anti-aliased wavy line is not pretty.
+ drawOptions.mAntialiasMode = AntialiasMode::NONE;
+ }
+ break;
+ default:
+ NS_ERROR("Invalid style value!");
+ return;
+ }
+
+ // The block-direction position should be set to the middle of the line.
+ if (aParams.vertical) {
+ rect.x += lineThickness / 2;
+ } else {
+ rect.y += lineThickness / 2;
+ }
+
+ switch (aParams.style) {
+ case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+ case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
+ Point p1 = rect.TopLeft();
+ Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
+ aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+ return;
+ }
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
+ /**
+ * We are drawing double line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * | |
+ * | |
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * +-------------------------------------------+
+ */
+ Point p1 = rect.TopLeft();
+ Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
+ aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+
+ if (aParams.vertical) {
+ rect.width -= lineThickness;
+ } else {
+ rect.height -= lineThickness;
+ }
+
+ p1 = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
+ p2 = rect.BottomRight();
+ aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+ return;
+ }
+ case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
+ /**
+ * We are drawing wavy line as:
+ *
+ * P: Path, X: Painted pixel
+ *
+ * +---------------------------------------+
+ * XX|X XXXXXX XXXXXX |
+ * PP|PX XPPPPPPX XPPPPPPX | ^
+ * XX|XPX XPXXXXXXPX XPXXXXXXPX| |
+ * | XPX XPX XPX XPX XP|X |adv
+ * | XPXXXXXXPX XPXXXXXXPX X|PX |
+ * | XPPPPPPX XPPPPPPX |XPX v
+ * | XXXXXX XXXXXX | XX
+ * +---------------------------------------+
+ * <---><---> ^
+ * adv flatLengthAtVertex rightMost
+ *
+ * 1. Always starts from top-left of the drawing area, however, we need
+ * to draw the line from outside of the rect. Because the start
+ * point of the line is not good style if we draw from inside it.
+ * 2. First, draw horizontal line from outside the rect to top-left of
+ * the rect;
+ * 3. Goes down to bottom of the area at 45 degrees.
+ * 4. Slides to right horizontaly, see |flatLengthAtVertex|.
+ * 5. Goes up to top of the area at 45 degrees.
+ * 6. Slides to right horizontaly.
+ * 7. Repeat from 2 until reached to right-most edge of the area.
+ *
+ * In the vertical case, swap horizontal and vertical coordinates and
+ * directions in the above description.
+ */
+
+ Float& rectICoord = aParams.vertical ? rect.y : rect.x;
+ Float& rectISize = aParams.vertical ? rect.height : rect.width;
+ const Float rectBSize = aParams.vertical ? rect.width : rect.height;
+
+ const Float adv = rectBSize - lineThickness;
+ const Float flatLengthAtVertex =
+ std::max((lineThickness - 1.0) * 2.0, 1.0);
+
+ // Align the start of wavy lines to the nearest ancestor block.
+ const Float cycleLength = 2 * (adv + flatLengthAtVertex);
+ rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style, rect,
+ aParams.icoordInFrame,
+ cycleLength, aParams.vertical);
+ // figure out if we can trim whole cycles from the left and right edges
+ // of the line, to try and avoid creating an unnecessarily long and
+ // complex path
+ const Float dirtyRectICoord = aParams.vertical ? aParams.dirtyRect.y
+ : aParams.dirtyRect.x;
+ int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
+ if (skipCycles > 0) {
+ rectICoord += skipCycles * cycleLength;
+ rectISize -= skipCycles * cycleLength;
+ }
+
+ rectICoord += lineThickness / 2.0;
+ Point pt(rect.TopLeft());
+ Float& ptICoord = aParams.vertical ? pt.y : pt.x;
+ Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
+ if (aParams.vertical) {
+ ptBCoord += adv + lineThickness / 2.0;
+ }
+ Float iCoordLimit = ptICoord + rectISize + lineThickness;
+
+ const Float dirtyRectIMost = aParams.vertical ?
+ aParams.dirtyRect.YMost() : aParams.dirtyRect.XMost();
+ skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
+ if (skipCycles > 0) {
+ iCoordLimit -= skipCycles * cycleLength;
+ }
+
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ RefPtr<Path> path;
+
+ ptICoord -= lineThickness;
+ builder->MoveTo(pt); // 1
+
+ ptICoord = rectICoord;
+ builder->LineTo(pt); // 2
+
+ // In vertical mode, to go "down" relative to the text we need to
+ // decrease the block coordinate, whereas in horizontal we increase
+ // it. So the sense of this flag is effectively inverted.
+ bool goDown = aParams.vertical ? false : true;
+ uint32_t iter = 0;
+ while (ptICoord < iCoordLimit) {
+ if (++iter > 1000) {
+ // stroke the current path and start again, to avoid pathological
+ // behavior in cairo with huge numbers of path segments
+ path = builder->Finish();
+ aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
+ builder = aDrawTarget.CreatePathBuilder();
+ builder->MoveTo(pt);
+ iter = 0;
+ }
+ ptICoord += adv;
+ ptBCoord += goDown ? adv : -adv;
+
+ builder->LineTo(pt); // 3 and 5
+
+ ptICoord += flatLengthAtVertex;
+ builder->LineTo(pt); // 4 and 6
+
+ goDown = !goDown;
+ }
+ path = builder->Finish();
+ aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
+ return;
+ }
+ default:
+ NS_ERROR("Invalid style value!");
+ }
+}
+
+Rect
+nsCSSRendering::DecorationLineToPath(const PaintDecorationLineParams& aParams)
+{
+ NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
+ "aStyle is none");
+
+ Rect path; // To benefit from RVO, we return this from all return points
+
+ Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
+ if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
+ return path;
+ }
+
+ if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
+ aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
+ aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
+ NS_ERROR("Invalid decoration value!");
+ return path;
+ }
+
+ if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
+ // For the moment, we support only solid text decorations.
+ return path;
+ }
+
+ Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
+
+ // The block-direction position should be set to the middle of the line.
+ if (aParams.vertical) {
+ rect.x += lineThickness / 2;
+ path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
+ Size(lineThickness, rect.Height()));
+ } else {
+ rect.y += lineThickness / 2;
+ path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
+ Size(rect.Width(), lineThickness));
+ }
+
+ return path;
+}
+
+nsRect
+nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
+ const DecorationRectParams& aParams)
+{
+ NS_ASSERTION(aPresContext, "aPresContext is null");
+ NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
+ "aStyle is none");
+
+ gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
+ // The rect values are already rounded to nearest device pixels.
+ nsRect r;
+ r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
+ r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
+ r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
+ r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
+ return r;
+}
+
+gfxRect
+nsCSSRendering::GetTextDecorationRectInternal(const Point& aPt,
+ const DecorationRectParams& aParams)
+{
+ NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
+ "Invalid aStyle value");
+
+ if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
+ return gfxRect(0, 0, 0, 0);
+
+ bool canLiftUnderline = aParams.descentLimit >= 0.0;
+
+ gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
+ gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
+
+ // 'left' and 'right' are relative to the line, so for vertical writing modes
+ // they will actually become top and bottom of the rendered line.
+ // Similarly, aLineSize.width and .height are actually length and thickness
+ // of the line, which runs horizontally or vertically according to aVertical.
+ const gfxFloat left = floor(iCoord + 0.5),
+ right = floor(iCoord + aParams.lineSize.width + 0.5);
+
+ // We compute |r| as if for a horizontal text run, and then swap vertical
+ // and horizontal coordinates at the end if vertical was requested.
+ gfxRect r(left, 0, right - left, 0);
+
+ gfxFloat lineThickness = NS_round(aParams.lineSize.height);
+ lineThickness = std::max(lineThickness, 1.0);
+
+ gfxFloat ascent = NS_round(aParams.ascent);
+ gfxFloat descentLimit = floor(aParams.descentLimit);
+
+ gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0);
+ r.height = lineThickness;
+ if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
+ /**
+ * We will draw double line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * | | ^
+ * | | | gap
+ * | | v
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
+ * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
+ * +-------------------------------------------+
+ */
+ gfxFloat gap = NS_round(lineThickness / 2.0);
+ gap = std::max(gap, 1.0);
+ r.height = lineThickness * 2.0 + gap;
+ if (canLiftUnderline) {
+ if (r.Height() > suggestedMaxRectHeight) {
+ // Don't shrink the line height, because the thickness has some meaning.
+ // We can just shrink the gap at this time.
+ r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
+ }
+ }
+ } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
+ /**
+ * We will draw wavy line as:
+ *
+ * +-------------------------------------------+
+ * |XXXXX XXXXXX XXXXXX | ^
+ * |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
+ * |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
+ * | XXX XXX XXX XXX XX|
+ * | XXXXXXXXXX XXXXXXXXXX X|
+ * | XXXXXXXX XXXXXXXX |
+ * | XXXXXX XXXXXX |
+ * +-------------------------------------------+
+ */
+ r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
+ if (canLiftUnderline) {
+ if (r.Height() > suggestedMaxRectHeight) {
+ // Don't shrink the line height even if there is not enough space,
+ // because the thickness has some meaning. E.g., the 1px wavy line and
+ // 2px wavy line can be used for different meaning in IME selections
+ // at same time.
+ r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
+ }
+ }
+ }
+
+ gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
+ gfxFloat offset = 0.0;
+ switch (aParams.decoration) {
+ case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE:
+ offset = aParams.offset;
+ if (canLiftUnderline) {
+ if (descentLimit < -offset + r.Height()) {
+ // If we can ignore the offset and the decoration line is overflowing,
+ // we should align the bottom edge of the decoration line rect if it's
+ // possible. Otherwise, we should lift up the top edge of the rect as
+ // far as possible.
+ gfxFloat offsetBottomAligned = -descentLimit + r.Height();
+ gfxFloat offsetTopAligned = 0.0;
+ offset = std::min(offsetBottomAligned, offsetTopAligned);
+ }
+ }
+ break;
+ case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE:
+ offset = aParams.offset - lineThickness + r.Height();
+ break;
+ case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: {
+ gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
+ extra = std::max(extra, lineThickness);
+ offset = aParams.offset - lineThickness + extra;
+ break;
+ }
+ default:
+ NS_ERROR("Invalid decoration value!");
+ }
+
+ if (aParams.vertical) {
+ r.y = baseline + floor(offset + 0.5);
+ Swap(r.x, r.y);
+ Swap(r.width, r.height);
+ } else {
+ r.y = baseline - floor(offset + 0.5);
+ }
+
+ return r;
+}
+
+// ------------------
+// ImageRenderer
+// ------------------
+nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
+ const nsStyleImage* aImage,
+ uint32_t aFlags)
+ : mForFrame(aForFrame)
+ , mImage(aImage)
+ , mType(aImage->GetType())
+ , mImageContainer(nullptr)
+ , mGradientData(nullptr)
+ , mPaintServerFrame(nullptr)
+ , mPrepareResult(DrawResult::NOT_READY)
+ , mSize(0, 0)
+ , mFlags(aFlags)
+ , mExtendMode(ExtendMode::CLAMP)
+ , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
+{
+}
+
+nsImageRenderer::~nsImageRenderer()
+{
+}
+
+static bool
+ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
+ return false;
+ }
+
+ if (aImage->GetType() != eStyleImageType_Image) {
+ return false;
+ }
+
+ imgRequestProxy* req = aImage->GetImageData();
+ if (!req) {
+ return false;
+ }
+
+ uint32_t status = 0;
+ if (NS_FAILED(req->GetImageStatus(&status))) {
+ return false;
+ }
+
+ if (status & imgIRequest::STATUS_ERROR) {
+ // The image is "complete" since it's a corrupt image. If we created an
+ // imgIContainer at all, return true.
+ nsCOMPtr<imgIContainer> image;
+ req->GetImage(getter_AddRefs(image));
+ return bool(image);
+ }
+
+ if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ // We must have loaded all of the image's data and the size must be
+ // available, or else sync decoding won't be able to decode the image.
+ return false;
+ }
+
+ return true;
+}
+
+bool
+nsImageRenderer::PrepareImage()
+{
+ if (mImage->IsEmpty()) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ if (!mImage->IsComplete()) {
+ // Make sure the image is actually decoding.
+ mImage->StartDecoding();
+
+ // Check again to see if we finished.
+ // We cannot prepare the image for rendering if it is not fully loaded.
+ // Special case: If we requested a sync decode and the image has loaded, push
+ // on through because the Draw() will do a sync decode then.
+ if (!mImage->IsComplete() &&
+ !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
+ mPrepareResult = DrawResult::NOT_READY;
+ return false;
+ }
+ }
+
+ switch (mType) {
+ case eStyleImageType_Image: {
+ MOZ_ASSERT(mImage->GetImageData(),
+ "must have image data, since we checked IsEmpty above");
+ nsCOMPtr<imgIContainer> srcImage;
+ DebugOnly<nsresult> rv =
+ mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
+ "If GetImage() is failing, mImage->IsComplete() "
+ "should have returned false");
+
+ if (!mImage->GetCropRect()) {
+ mImageContainer.swap(srcImage);
+ } else {
+ nsIntRect actualCropRect;
+ bool isEntireImage;
+ bool success =
+ mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
+ NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
+ if (!success || actualCropRect.IsEmpty()) {
+ // The cropped image has zero size
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+ if (isEntireImage) {
+ // The cropped image is identical to the source image
+ mImageContainer.swap(srcImage);
+ } else {
+ nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
+ actualCropRect,
+ Nothing());
+ mImageContainer.swap(subImage);
+ }
+ }
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ }
+ case eStyleImageType_Gradient:
+ mGradientData = mImage->GetGradientData();
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ case eStyleImageType_Element:
+ {
+ nsAutoString elementId =
+ NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
+ mForFrame->GetContent()->GetUncomposedDoc(), base);
+ nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
+ targetURI, mForFrame->FirstContinuation(),
+ nsSVGEffects::BackgroundImageProperty());
+ if (!property) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+
+ // If the referenced element is an <img>, <canvas>, or <video> element,
+ // prefer SurfaceFromElement as it's more reliable.
+ mImageElementSurface =
+ nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
+ if (!mImageElementSurface.GetSourceSurface()) {
+ nsIFrame* paintServerFrame = property->GetReferencedFrame();
+ // If there's no referenced frame, or the referenced frame is
+ // non-displayable SVG, then we have nothing valid to paint.
+ if (!paintServerFrame ||
+ (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ !paintServerFrame->IsFrameOfType(nsIFrame::eSVGPaintServer) &&
+ !static_cast<nsISVGChildFrame*>(do_QueryFrame(paintServerFrame)))) {
+ mPrepareResult = DrawResult::BAD_IMAGE;
+ return false;
+ }
+ mPaintServerFrame = paintServerFrame;
+ }
+
+ mPrepareResult = DrawResult::SUCCESS;
+ break;
+ }
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ return IsReady();
+}
+
+nsSize
+CSSSizeOrRatio::ComputeConcreteSize() const
+{
+ NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
+ if (mHasWidth && mHasHeight) {
+ return nsSize(mWidth, mHeight);
+ }
+ if (mHasWidth) {
+ nscoord height = NSCoordSaturatingNonnegativeMultiply(
+ mWidth,
+ double(mRatio.height) / mRatio.width);
+ return nsSize(mWidth, height);
+ }
+
+ MOZ_ASSERT(mHasHeight);
+ nscoord width = NSCoordSaturatingNonnegativeMultiply(
+ mHeight,
+ double(mRatio.width) / mRatio.height);
+ return nsSize(width, mHeight);
+}
+
+CSSSizeOrRatio
+nsImageRenderer::ComputeIntrinsicSize()
+{
+ NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
+ "before calling me");
+
+ CSSSizeOrRatio result;
+ switch (mType) {
+ case eStyleImageType_Image:
+ {
+ bool haveWidth, haveHeight;
+ CSSIntSize imageIntSize;
+ nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
+ result.mRatio, haveWidth, haveHeight);
+ if (haveWidth) {
+ result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
+ }
+ if (haveHeight) {
+ result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
+ }
+ break;
+ }
+ case eStyleImageType_Element:
+ {
+ // XXX element() should have the width/height of the referenced element,
+ // and that element's ratio, if it matches. If it doesn't match, it
+ // should have no width/height or ratio. See element() in CSS images:
+ // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
+ // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
+ // when fixing this!
+ if (mPaintServerFrame) {
+ // SVG images have no intrinsic size
+ if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ // The intrinsic image size for a generic nsIFrame paint server is
+ // the union of the border-box rects of all of its continuations,
+ // rounded to device pixels.
+ int32_t appUnitsPerDevPixel =
+ mForFrame->PresContext()->AppUnitsPerDevPixel();
+ result.SetSize(
+ IntSizeToAppUnits(
+ nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
+ ToNearestPixels(appUnitsPerDevPixel),
+ appUnitsPerDevPixel));
+ }
+ } else {
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+ "Surface should be ready.");
+ IntSize surfaceSize = mImageElementSurface.mSize;
+ result.SetSize(
+ nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
+ nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
+ }
+ break;
+ }
+ case eStyleImageType_Gradient:
+ // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
+ // intrinsic dimensions.
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ return result;
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
+ const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize)
+{
+ // The specified size is fully specified, just use that
+ if (aSpecifiedSize.IsConcrete()) {
+ return aSpecifiedSize.ComputeConcreteSize();
+ }
+
+ MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
+
+ if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
+ // no specified size, try using the intrinsic size
+ if (aIntrinsicSize.CanComputeConcreteSize()) {
+ return aIntrinsicSize.ComputeConcreteSize();
+ }
+
+ if (aIntrinsicSize.mHasWidth) {
+ return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
+ }
+ if (aIntrinsicSize.mHasHeight) {
+ return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
+ }
+
+ // couldn't use the intrinsic size either, revert to using the default size
+ return ComputeConstrainedSize(aDefaultSize,
+ aIntrinsicSize.mRatio,
+ CONTAIN);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
+
+ // The specified height is partial, try to compute the missing part.
+ if (aSpecifiedSize.mHasWidth) {
+ nscoord height;
+ if (aIntrinsicSize.HasRatio()) {
+ height = NSCoordSaturatingNonnegativeMultiply(
+ aSpecifiedSize.mWidth,
+ double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
+ } else if (aIntrinsicSize.mHasHeight) {
+ height = aIntrinsicSize.mHeight;
+ } else {
+ height = aDefaultSize.height;
+ }
+ return nsSize(aSpecifiedSize.mWidth, height);
+ }
+
+ MOZ_ASSERT(aSpecifiedSize.mHasHeight);
+ nscoord width;
+ if (aIntrinsicSize.HasRatio()) {
+ width = NSCoordSaturatingNonnegativeMultiply(
+ aSpecifiedSize.mHeight,
+ double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
+ } else if (aIntrinsicSize.mHasWidth) {
+ width = aIntrinsicSize.mWidth;
+ } else {
+ width = aDefaultSize.width;
+ }
+ return nsSize(width, aSpecifiedSize.mHeight);
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
+ const nsSize& aIntrinsicRatio,
+ FitType aFitType)
+{
+ if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
+ return aConstrainingSize;
+ }
+
+ float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
+ float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
+ nsSize size;
+ if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
+ size.width = aConstrainingSize.width;
+ size.height = NSCoordSaturatingNonnegativeMultiply(
+ aIntrinsicRatio.height, scaleX);
+ // If we're reducing the size by less than one css pixel, then just use the
+ // constraining size.
+ if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
+ size.height = aConstrainingSize.height;
+ }
+ } else {
+ size.width = NSCoordSaturatingNonnegativeMultiply(
+ aIntrinsicRatio.width, scaleY);
+ if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
+ size.width = aConstrainingSize.width;
+ }
+ size.height = aConstrainingSize.height;
+ }
+ return size;
+}
+
+/**
+ * mSize is the image's "preferred" size for this particular rendering, while
+ * the drawn (aka concrete) size is the actual rendered size after accounting
+ * for background-size etc.. The preferred size is most often the image's
+ * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
+ * the preferred size varies, depending on the specified and default sizes, see
+ * nsImageRenderer::Compute*Size.
+ *
+ * This distinction is necessary because the components of a vector image are
+ * specified with respect to its preferred size for a rendering situation, not
+ * to its actual rendered size. For example, consider a 4px wide background
+ * vector image with no height which contains a left-aligned
+ * 2px wide black rectangle with height 100%. If the background-size width is
+ * auto (or 4px), the vector image will render 4px wide, and the black rectangle
+ * will be 2px wide. If the background-size width is 8px, the vector image will
+ * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
+ * In both cases mSize.width will be 4px; but in the first case the returned
+ * width will be 4px, while in the second case the returned width will be 8px.
+ */
+void
+nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize)
+{
+ mSize.width = aIntrinsicSize.mHasWidth
+ ? aIntrinsicSize.mWidth
+ : aDefaultSize.width;
+ mSize.height = aIntrinsicSize.mHasHeight
+ ? aIntrinsicSize.mHeight
+ : aDefaultSize.height;
+}
+
+// Convert from nsImageRenderer flags to the flags we want to use for drawing in
+// the imgIContainer namespace.
+static uint32_t
+ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
+{
+ uint32_t drawFlags = imgIContainer::FLAG_NONE;
+ if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+ drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+ if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
+ drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ return drawFlags;
+}
+
+/*
+ * SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation: |
+ * | R' | | 0 0 0 0 0 | | R |
+ * | G' | | 0 0 0 0 0 | | G |
+ * | B' | = | 0 0 0 0 0 | * | B |
+ * | A' | | 0.2125 0.7154 0.0721 0 0 | | A |
+ * | 1 | | 0 0 0 0 1 | | 1 |
+ */
+static void
+RGBALuminanceOperation(uint8_t *aData,
+ int32_t aStride,
+ const IntSize &aSize)
+{
+ int32_t redFactor = 55; // 256 * 0.2125
+ int32_t greenFactor = 183; // 256 * 0.7154
+ int32_t blueFactor = 18; // 256 * 0.0721
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ uint32_t *pixel = (uint32_t*)(aData + aStride * y);
+ for (int32_t x = 0; x < aSize.width; x++) {
+ *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
+ (((*pixel & 0x0000FF00) >> 8) * greenFactor) +
+ ((*pixel & 0x000000FF) * blueFactor)) >> 8) << 24;
+ pixel++;
+ }
+ }
+}
+
+
+DrawResult
+nsImageRenderer::Draw(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsSize& aRepeatSize,
+ const CSSIntRect& aSrc)
+{
+ if (!IsReady()) {
+ NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ if (aDest.IsEmpty() || aFill.IsEmpty() ||
+ mSize.width <= 0 || mSize.height <= 0) {
+ return DrawResult::SUCCESS;
+ }
+
+ SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+ DrawResult result = DrawResult::SUCCESS;
+ RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
+ IntRect tmpDTRect;
+
+ if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+ gfxRect clipRect = ctx->GetClipExtents();
+ tmpDTRect = RoundedOut(ToRect(clipRect));
+ if (tmpDTRect.IsEmpty()) {
+ return DrawResult::SUCCESS;
+ }
+ RefPtr<DrawTarget> tempDT =
+ gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
+ tmpDTRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (!tempDT || !tempDT->IsValid()) {
+ gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
+ ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
+ if (!ctx) {
+ gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ }
+
+ switch (mType) {
+ case eStyleImageType_Image:
+ {
+ CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
+ result =
+ nsLayoutUtils::DrawBackgroundImage(*ctx,
+ aPresContext,
+ mImageContainer, imageSize,
+ samplingFilter,
+ aDest, aFill, aRepeatSize,
+ aAnchor, aDirtyRect,
+ ConvertImageRendererToDrawFlags(mFlags),
+ mExtendMode);
+ break;
+ }
+ case eStyleImageType_Gradient:
+ {
+ nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
+ mGradientData, aDirtyRect,
+ aDest, aFill, aRepeatSize, aSrc, mSize);
+ break;
+ }
+ case eStyleImageType_Element:
+ {
+ RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
+ aRenderingContext);
+ if (!drawable) {
+ NS_WARNING("Could not create drawable for element");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+ result =
+ nsLayoutUtils::DrawImage(*ctx,
+ aPresContext, image,
+ samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
+ ConvertImageRendererToDrawFlags(mFlags));
+ break;
+ }
+ case eStyleImageType_Null:
+ default:
+ break;
+ }
+
+ if (!tmpDTRect.IsEmpty()) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+ if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+ RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
+ return result;
+ }
+
+ RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
+ maskData->Unmap();
+ surf = maskData;
+ }
+
+ DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
+ dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
+ Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
+ }
+
+ return result;
+}
+
+already_AddRefed<gfxDrawable>
+nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
+ nsRenderingContext& aRenderingContext)
+{
+ NS_ASSERTION(mType == eStyleImageType_Element,
+ "DrawableForElement only makes sense if backed by an element");
+ if (mPaintServerFrame) {
+ // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
+ // DrawableFromPaintServer would have to return a DrawResult indicating
+ // whether any images could not be painted because they weren't fully
+ // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
+ // problems, as it won't help if there are image which haven't finished
+ // loading, but it's better than nothing.
+ int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect destRect = aImageRect - aImageRect.TopLeft();
+ nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
+ IntSize imageSize(roundedOut.width, roundedOut.height);
+ RefPtr<gfxDrawable> drawable =
+ nsSVGIntegrationUtils::DrawableFromPaintServer(
+ mPaintServerFrame, mForFrame, mSize, imageSize,
+ aRenderingContext.GetDrawTarget(),
+ aRenderingContext.ThebesContext()->CurrentMatrix(),
+ nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
+
+ return drawable.forget();
+ }
+ NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
+ mImageElementSurface.GetSourceSurface().get(),
+ mImageElementSurface.mSize);
+ return drawable.forget();
+}
+
+DrawResult
+nsImageRenderer::DrawBackground(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ const nsSize& aRepeatSize)
+{
+ if (!IsReady()) {
+ NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+ if (aDest.IsEmpty() || aFill.IsEmpty() ||
+ mSize.width <= 0 || mSize.height <= 0) {
+ return DrawResult::SUCCESS;
+ }
+
+ return Draw(aPresContext, aRenderingContext,
+ aDirty, aDest, aFill, aAnchor, aRepeatSize,
+ CSSIntRect(0, 0,
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
+}
+
+/**
+ * Compute the size and position of the master copy of the image. I.e., a single
+ * tile used to fill the dest rect.
+ * aFill The destination rect to be filled
+ * aHFill and aVFill are the repeat patterns for the component -
+ * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
+ * aUnitSize The size of the source rect in dest coords.
+ */
+static nsRect
+ComputeTile(nsRect& aFill,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize,
+ nsSize& aRepeatSize)
+{
+ nsRect tile;
+ switch (aHFill) {
+ case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+ tile.x = aFill.x;
+ tile.width = aFill.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+ tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
+ tile.width = aUnitSize.width;
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+ tile.x = aFill.x;
+ tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
+ aRepeatSize.width = tile.width;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+ {
+ nscoord space;
+ aRepeatSize.width =
+ ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
+ tile.x = aFill.x + space;
+ tile.width = aUnitSize.width;
+ aFill.x = tile.x;
+ aFill.width = aFill.width - space * 2;
+ }
+ break;
+ default:
+ NS_NOTREACHED("unrecognized border-image fill style");
+ }
+
+ switch (aVFill) {
+ case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+ tile.y = aFill.y;
+ tile.height = aFill.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+ tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
+ tile.height = aUnitSize.height;
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+ tile.y = aFill.y;
+ tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
+ aRepeatSize.height = tile.height;
+ break;
+ case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+ {
+ nscoord space;
+ aRepeatSize.height =
+ ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
+ tile.y = aFill.y + space;
+ tile.height = aUnitSize.height;
+ aFill.y = tile.y;
+ aFill.height = aFill.height - space * 2;
+ }
+ break;
+ default:
+ NS_NOTREACHED("unrecognized border-image fill style");
+ }
+
+ return tile;
+}
+
+/**
+ * Returns true if the given set of arguments will require the tiles which fill
+ * the dest rect to be scaled from the source tile. See comment on ComputeTile
+ * for argument descriptions.
+ */
+static bool
+RequiresScaling(const nsRect& aFill,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize)
+{
+ // If we have no tiling in either direction, we can skip the intermediate
+ // scaling step.
+ return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
+ aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
+ (aUnitSize.width != aFill.width ||
+ aUnitSize.height != aFill.height);
+}
+
+DrawResult
+nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aFill,
+ const CSSIntRect& aSrc,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize,
+ uint8_t aIndex,
+ const Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio)
+{
+ if (!IsReady()) {
+ NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+ return DrawResult::BAD_ARGS;
+ }
+ if (aFill.IsEmpty() || aSrc.IsEmpty()) {
+ return DrawResult::SUCCESS;
+ }
+
+ if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
+ nsCOMPtr<imgIContainer> subImage;
+
+ // To draw one portion of an image into a border component, we stretch that
+ // portion to match the size of that border component and then draw onto.
+ // However, preserveAspectRatio attribute of a SVG image may break this rule.
+ // To get correct rendering result, we add
+ // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
+ // preserveAspectRatio attribute, and always do non-uniform stretch.
+ uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
+ imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
+ // For those SVG image sources which don't have fixed aspect ratio (i.e.
+ // without viewport size and viewBox), we should scale the source uniformly
+ // after the viewport size is decided by "Default Sizing Algorithm".
+ if (!aHasIntrinsicRatio) {
+ drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
+ }
+ // Retrieve or create the subimage we'll draw.
+ nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
+ if (mType == eStyleImageType_Image) {
+ if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
+ subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
+ mImage->SetSubImage(aIndex, subImage);
+ }
+ } else {
+ // This path, for eStyleImageType_Element, is currently slower than it
+ // needs to be because we don't cache anything. (In particular, if we have
+ // to draw to a temporary surface inside ClippedImage, we don't cache that
+ // temporary surface since we immediately throw the ClippedImage we create
+ // here away.) However, if we did cache, we'd need to know when to
+ // invalidate that cache, and it's not clear that it's worth the trouble
+ // since using border-image with -moz-element is rare.
+
+ RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
+ aRenderingContext);
+ if (!drawable) {
+ NS_WARNING("Could not create drawable for element");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+ subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
+ }
+
+ MOZ_ASSERT_IF(aSVGViewportSize,
+ subImage->GetType() == imgIContainer::TYPE_VECTOR);
+
+ SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+
+ if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
+ return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ subImage,
+ samplingFilter,
+ aFill, aDirtyRect,
+ nullptr,
+ drawFlags);
+ }
+
+ nsSize repeatSize;
+ nsRect fillRect(aFill);
+ nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
+ CSSIntSize imageSize(srcRect.width, srcRect.height);
+ return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ subImage, imageSize, samplingFilter,
+ tile, fillRect, repeatSize,
+ tile.TopLeft(), aDirtyRect,
+ drawFlags,
+ ExtendMode::CLAMP);
+ }
+
+ nsSize repeatSize(aFill.Size());
+ nsRect fillRect(aFill);
+ nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
+ ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
+ : fillRect;
+ return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
+ fillRect, destTile.TopLeft(), repeatSize, aSrc);
+}
+
+bool
+nsImageRenderer::IsRasterImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer)
+ return false;
+ return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
+}
+
+bool
+nsImageRenderer::IsAnimatedImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer)
+ return false;
+ bool animated = false;
+ if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
+ return true;
+
+ return false;
+}
+
+already_AddRefed<imgIContainer>
+nsImageRenderer::GetImage()
+{
+ if (mType != eStyleImageType_Image || !mImageContainer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<imgIContainer> image = mImageContainer;
+ return image.forget();
+}
+
+void
+nsImageRenderer::PurgeCacheForViewportChange(
+ const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
+{
+ // Check if we should flush the cached data - only vector images need to do
+ // the check since they might not have fixed ratio.
+ if (mImageContainer &&
+ mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
+ }
+}
+
+#define MAX_BLUR_RADIUS 300
+#define MAX_SPREAD_RADIUS 50
+
+static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFloat aScaleX,
+ gfxFloat aScaleY)
+{
+ // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
+ // standard deviation of the blur should be half the given blur value.
+ gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
+
+ return gfxPoint(std::min((blurStdDev * aScaleX),
+ gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
+ std::min((blurStdDev * aScaleY),
+ gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
+}
+
+static inline IntSize
+ComputeBlurRadius(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFloat aScaleX = 1.0,
+ gfxFloat aScaleY = 1.0)
+{
+ gfxPoint scaledBlurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel,
+ aScaleX, aScaleY);
+ return
+ gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
+}
+
+// -----
+// nsContextBoxBlur
+// -----
+gfxContext*
+nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
+ nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel,
+ gfxContext* aDestinationCtx,
+ const nsRect& aDirtyRect,
+ const gfxRect* aSkipRect,
+ uint32_t aFlags)
+{
+ if (aRect.IsEmpty()) {
+ mContext = nullptr;
+ return nullptr;
+ }
+
+ IntSize blurRadius;
+ IntSize spreadRadius;
+ GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
+ aBlurRadius, aSpreadRadius,
+ blurRadius, spreadRadius);
+
+ mDestinationCtx = aDestinationCtx;
+
+ // If not blurring, draw directly onto the destination device
+ if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
+ spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
+ !(aFlags & FORCE_MASK)) {
+ mContext = aDestinationCtx;
+ return mContext;
+ }
+
+ // Convert from app units to device pixels
+ gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
+
+ gfxRect dirtyRect =
+ nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
+ dirtyRect.RoundOut();
+
+ gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+ rect = transform.TransformBounds(rect);
+
+ mPreTransformed = !transform.IsIdentity();
+
+ // Create the temporary surface for blurring
+ dirtyRect = transform.TransformBounds(dirtyRect);
+ if (aSkipRect) {
+ gfxRect skipRect = transform.TransformBounds(*aSkipRect);
+ mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
+ blurRadius, &dirtyRect, &skipRect);
+ } else {
+ mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
+ blurRadius, &dirtyRect, nullptr);
+ }
+
+ if (mContext) {
+ // we don't need to blur if skipRect is equal to rect
+ // and mContext will be nullptr
+ mContext->Multiply(transform);
+ }
+ return mContext;
+}
+
+void
+nsContextBoxBlur::DoPaint()
+{
+ if (mContext == mDestinationCtx) {
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
+
+ if (mPreTransformed) {
+ mDestinationCtx->SetMatrix(gfxMatrix());
+ }
+
+ mAlphaBoxBlur.Paint(mDestinationCtx);
+}
+
+gfxContext*
+nsContextBoxBlur::GetContext()
+{
+ return mContext;
+}
+
+/* static */ nsMargin
+nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel)
+{
+ IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
+
+ nsMargin result;
+ result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
+ result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
+ return result;
+}
+
+/* static */ void
+nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
+ const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel,
+ RectCornerRadii* aCornerRadii,
+ nscoord aBlurRadius,
+ const Color& aShadowColor,
+ const nsRect& aDirtyRect,
+ const gfxRect& aSkipRect)
+{
+ DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
+
+ if (aRect.IsEmpty()) {
+ return;
+ }
+
+ Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
+
+ if (aBlurRadius <= 0) {
+ ColorPattern color(ToDeviceColor(aShadowColor));
+ if (aCornerRadii) {
+ RefPtr<Path> roundedRect = MakePathForRoundedRect(aDestDrawTarget,
+ shadowGfxRect,
+ *aCornerRadii);
+ aDestDrawTarget.Fill(roundedRect, color);
+ } else {
+ aDestDrawTarget.FillRect(shadowGfxRect, color);
+ }
+ return;
+ }
+
+ gfxFloat scaleX = 1;
+ gfxFloat scaleY = 1;
+
+ // Do blurs in device space when possible.
+ // Chrome/Skia always does the blurs in device space
+ // and will sometimes get incorrect results (e.g. rotated blurs)
+ gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+ // XXX: we could probably handle negative scales but for now it's easier just to fallback
+ if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
+ scaleX = transform._11;
+ scaleY = transform._22;
+ aDestinationCtx->SetMatrix(gfxMatrix());
+ } else {
+ transform = gfxMatrix();
+ }
+
+ gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+
+ gfxRect dirtyRect =
+ nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
+ dirtyRect.RoundOut();
+
+ gfxRect shadowThebesRect = transform.TransformBounds(ThebesRect(shadowGfxRect));
+ dirtyRect = transform.TransformBounds(dirtyRect);
+ gfxRect skipRect = transform.TransformBounds(aSkipRect);
+
+ if (aCornerRadii) {
+ aCornerRadii->Scale(scaleX, scaleY);
+ }
+
+ gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
+ shadowThebesRect,
+ aCornerRadii,
+ blurStdDev,
+ aShadowColor,
+ dirtyRect,
+ skipRect);
+}
+
+/* static */ void
+nsContextBoxBlur::GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ nscoord aBlurRadius,
+ nscoord aSpreadRadius,
+ IntSize& aOutBlurRadius,
+ IntSize& aOutSpreadRadius,
+ bool aConstrainSpreadRadius)
+{
+ // Do blurs in device space when possible.
+ // Chrome/Skia always does the blurs in device space
+ // and will sometimes get incorrect results (e.g. rotated blurs)
+ Matrix transform = aDestDrawTarget->GetTransform();
+ // XXX: we could probably handle negative scales but for now it's easier just to fallback
+ gfxFloat scaleX, scaleY;
+ if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
+ scaleX = 1;
+ scaleY = 1;
+ } else {
+ scaleX = transform._11;
+ scaleY = transform._22;
+ }
+
+ // compute a large or smaller blur radius
+ aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+ aOutSpreadRadius =
+ IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
+ int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
+
+
+ if (aConstrainSpreadRadius) {
+ aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
+ aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
+ }
+}
+
+/* static */ bool
+nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
+ Rect aDestinationRect,
+ Rect aShadowClipRect,
+ Color& aShadowColor,
+ nscoord aBlurRadiusAppUnits,
+ nscoord aSpreadDistanceAppUnits,
+ int32_t aAppUnitsPerDevPixel,
+ bool aHasBorderRadius,
+ RectCornerRadii& aInnerClipRectRadii,
+ Rect aSkipRect, Point aShadowOffset)
+{
+ if (aDestinationRect.IsEmpty()) {
+ mContext = nullptr;
+ return false;
+ }
+
+ gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
+
+ IntSize blurRadius;
+ IntSize spreadRadius;
+ // Convert the blur and spread radius to device pixels
+ bool constrainSpreadRadius = false;
+ GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
+ aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
+ blurRadius, spreadRadius, constrainSpreadRadius);
+
+ // The blur and spread radius are scaled already, so scale all
+ // input data to the blur. This way, we don't have to scale the min
+ // inset blur to the invert of the dest context, then rescale it back
+ // when we draw to the destination surface.
+ gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
+ Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix());
+
+ // XXX: we could probably handle negative scales but for now it's easier just to fallback
+ if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
+ // If we don't have a rotation, we're pre-transforming all the rects.
+ aDestinationCtx->SetMatrix(gfxMatrix());
+ } else {
+ // Don't touch anything, we have a rotation.
+ transform = Matrix();
+ }
+
+ Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
+ Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
+ Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
+
+ transformedDestRect.Round();
+ transformedShadowClipRect.Round();
+ transformedSkipRect.RoundIn();
+
+ for (size_t i = 0; i < 4; i++) {
+ aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
+ aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
+ }
+
+ mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
+ transformedShadowClipRect,
+ blurRadius, spreadRadius,
+ aShadowColor, aHasBorderRadius,
+ aInnerClipRectRadii, transformedSkipRect,
+ aShadowOffset);
+ return true;
+}
diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h
new file mode 100644
index 000000000..791e9656e
--- /dev/null
+++ b/layout/base/nsCSSRendering.h
@@ -0,0 +1,1030 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#ifndef nsCSSRendering_h___
+#define nsCSSRendering_h___
+
+#include "gfxBlur.h"
+#include "gfxContext.h"
+#include "imgIContainer.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleStruct.h"
+#include "nsIFrame.h"
+
+class gfxDrawable;
+class nsStyleContext;
+class nsPresContext;
+class nsRenderingContext;
+
+namespace mozilla {
+
+namespace gfx {
+struct Color;
+class DrawTarget;
+} // namespace gfx
+
+namespace layers {
+class ImageContainer;
+} // namespace layers
+
+// A CSSSizeOrRatio represents a (possibly partially specified) size for use
+// in computing image sizes. Either or both of the width and height might be
+// given. A ratio of width to height may also be given. If we at least two
+// of these then we can compute a concrete size, that is a width and height.
+struct CSSSizeOrRatio
+{
+ CSSSizeOrRatio()
+ : mRatio(0, 0)
+ , mHasWidth(false)
+ , mHasHeight(false) {}
+
+ bool CanComputeConcreteSize() const
+ {
+ return mHasWidth + mHasHeight + HasRatio() >= 2;
+ }
+ bool IsConcrete() const { return mHasWidth && mHasHeight; }
+ bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
+ bool IsEmpty() const
+ {
+ return (mHasWidth && mWidth <= 0) ||
+ (mHasHeight && mHeight <= 0) ||
+ mRatio.width <= 0 || mRatio.height <= 0;
+ }
+
+ // CanComputeConcreteSize must return true when ComputeConcreteSize is
+ // called.
+ nsSize ComputeConcreteSize() const;
+
+ void SetWidth(nscoord aWidth)
+ {
+ mWidth = aWidth;
+ mHasWidth = true;
+ if (mHasHeight) {
+ mRatio = nsSize(mWidth, mHeight);
+ }
+ }
+ void SetHeight(nscoord aHeight)
+ {
+ mHeight = aHeight;
+ mHasHeight = true;
+ if (mHasWidth) {
+ mRatio = nsSize(mWidth, mHeight);
+ }
+ }
+ void SetSize(const nsSize& aSize)
+ {
+ mWidth = aSize.width;
+ mHeight = aSize.height;
+ mHasWidth = true;
+ mHasHeight = true;
+ mRatio = aSize;
+ }
+ void SetRatio(const nsSize& aRatio)
+ {
+ MOZ_ASSERT(!mHasWidth || !mHasHeight,
+ "Probably shouldn't be setting a ratio if we have a concrete size");
+ mRatio = aRatio;
+ }
+
+ nsSize mRatio;
+ nscoord mWidth;
+ nscoord mHeight;
+ bool mHasWidth;
+ bool mHasHeight;
+};
+
+enum class PaintBorderFlags : uint8_t
+{
+ SYNC_DECODE_IMAGES = 1 << 0
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags)
+
+} // namespace mozilla
+
+/**
+ * This is a small wrapper class to encapsulate image drawing that can draw an
+ * nsStyleImage image, which may internally be a real image, a sub image, or a
+ * CSS gradient.
+ *
+ * @note Always call the member functions in the order of PrepareImage(),
+ * SetSize(), and Draw*().
+ */
+class nsImageRenderer {
+public:
+ typedef mozilla::image::DrawResult DrawResult;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ FLAG_PAINTING_TO_WINDOW = 0x02
+ };
+ enum FitType
+ {
+ CONTAIN,
+ COVER
+ };
+
+ nsImageRenderer(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags);
+ ~nsImageRenderer();
+ /**
+ * Populates member variables to get ready for rendering.
+ * @return true iff the image is ready, and there is at least a pixel to
+ * draw.
+ */
+ bool PrepareImage();
+
+ /**
+ * The three Compute*Size functions correspond to the sizing algorthms and
+ * definitions from the CSS Image Values and Replaced Content spec. See
+ * http://dev.w3.org/csswg/css-images-3/#sizing .
+ */
+
+ /**
+ * Compute the intrinsic size of the image as defined in the CSS Image Values
+ * spec. The intrinsic size is the unscaled size which the image would ideally
+ * like to be in app units.
+ */
+ mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
+
+ /**
+ * Computes the placement for a background image, or for the image data
+ * inside of a replaced element.
+ *
+ * @param aPos The CSS <position> value that specifies the image's position.
+ * @param aOriginBounds The box to which the tiling position should be
+ * relative. For background images, this should correspond to
+ * 'background-origin' for the frame, except when painting on the
+ * canvas, in which case the origin bounds should be the bounds
+ * of the root element's frame. For a replaced element, this should
+ * be the element's content-box.
+ * @param aTopLeft [out] The top-left corner where an image tile should be
+ * drawn.
+ * @param aAnchorPoint [out] A point which should be pixel-aligned by
+ * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
+ * CSS specifies a percentage (including 'right' or 'bottom'), in
+ * which case it's that percentage within of aOriginBounds. So
+ * 'right' would set aAnchorPoint.x to aOriginBounds.XMost().
+ *
+ * Points are returned relative to aOriginBounds.
+ */
+ static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
+ const nsSize& aOriginBounds,
+ const nsSize& aImageSize,
+ nsPoint* aTopLeft,
+ nsPoint* aAnchorPoint);
+
+ /**
+ * Compute the size of the rendered image using either the 'cover' or
+ * 'contain' constraints (aFitType).
+ * aIntrinsicRatio may be an invalid ratio, that is one or both of its
+ * dimensions can be less than or equal to zero.
+ */
+ static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
+ const nsSize& aIntrinsicRatio,
+ FitType aFitType);
+ /**
+ * Compute the size of the rendered image (the concrete size) where no cover/
+ * contain constraints are given. The 'default algorithm' from the CSS Image
+ * Values spec.
+ */
+ static nsSize ComputeConcreteSize(const mozilla::CSSSizeOrRatio& aSpecifiedSize,
+ const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Set this image's preferred size. This will be its intrinsic size where
+ * specified and the default size where it is not. Used as the unscaled size
+ * when rendering the image.
+ */
+ void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+ const nsSize& aDefaultSize);
+
+ /**
+ * Draws the image to the target rendering context using background-specific
+ * arguments.
+ * @see nsLayoutUtils::DrawImage() for parameters.
+ */
+ DrawResult DrawBackground(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ const nsSize& aRepeatSize);
+
+ /**
+ * Draw the image to a single component of a border-image style rendering.
+ * aFill The destination rect to be drawn into
+ * aSrc is the part of the image to be rendered into a tile (aUnitSize in
+ * aFill), if aSrc and the dest tile are different sizes, the image will be
+ * scaled to map aSrc onto the dest tile.
+ * aHFill and aVFill are the repeat patterns for the component -
+ * NS_STYLE_BORDER_IMAGE_REPEAT_*
+ * aUnitSize The scaled size of a single source rect (in destination coords)
+ * aIndex identifies the component: 0 1 2
+ * 3 4 5
+ * 6 7 8
+ * aSVGViewportSize The image size evaluated by default sizing algorithm.
+ * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
+ * the drawing image directly, otherwise, pass Some() with viewport size
+ * evaluated from default sizing algorithm.
+ * aHasIntrinsicRatio is used to record if the source image has fixed
+ * intrinsic ratio.
+ */
+ DrawResult
+ DrawBorderImageComponent(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aFill,
+ const mozilla::CSSIntRect& aSrc,
+ uint8_t aHFill,
+ uint8_t aVFill,
+ const nsSize& aUnitSize,
+ uint8_t aIndex,
+ const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio);
+
+ bool IsRasterImage();
+ bool IsAnimatedImage();
+
+ /// Retrieves the image associated with this nsImageRenderer, if there is one.
+ already_AddRefed<imgIContainer> GetImage();
+
+ bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
+ DrawResult PrepareResult() const { return mPrepareResult; }
+ void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
+ void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
+ void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasRatio);
+
+private:
+ /**
+ * Draws the image to the target rendering context.
+ * aSrc is a rect on the source image which will be mapped to aDest; it's
+ * currently only used for gradients.
+ *
+ * @see nsLayoutUtils::DrawImage() for other parameters.
+ */
+ DrawResult Draw(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc);
+
+ /**
+ * Helper method for creating a gfxDrawable from mPaintServerFrame or
+ * mImageElementSurface.
+ * Requires mType is eStyleImageType_Element.
+ * Returns null if we cannot create the drawable.
+ */
+ already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
+ nsRenderingContext& aRenderingContext);
+
+ nsIFrame* mForFrame;
+ const nsStyleImage* mImage;
+ nsStyleImageType mType;
+ nsCOMPtr<imgIContainer> mImageContainer;
+ RefPtr<nsStyleGradient> mGradientData;
+ nsIFrame* mPaintServerFrame;
+ nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
+ DrawResult mPrepareResult;
+ nsSize mSize; // unscaled size of the image, in app units
+ uint32_t mFlags;
+ mozilla::gfx::ExtendMode mExtendMode;
+ uint8_t mMaskOp;
+};
+
+/**
+ * A struct representing all the information needed to paint a background
+ * image to some target, taking into account all CSS background-* properties.
+ * See PrepareImageLayer.
+ */
+struct nsBackgroundLayerState {
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+
+ /**
+ * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
+ */
+ nsBackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage,
+ uint32_t aFlags)
+ : mImageRenderer(aForFrame, aImage, aFlags)
+ {}
+
+ /**
+ * The nsImageRenderer that will be used to draw the background.
+ */
+ nsImageRenderer mImageRenderer;
+ /**
+ * A rectangle that one copy of the image tile is mapped onto. Same
+ * coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsRect mDestArea;
+ /**
+ * The actual rectangle that should be filled with (complete or partial)
+ * image tiles. Same coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsRect mFillArea;
+ /**
+ * The anchor point that should be snapped to a pixel corner. Same
+ * coordinate system as aBorderArea/aBGClipRect passed into
+ * PrepareImageLayer.
+ */
+ nsPoint mAnchor;
+ /**
+ * The background-repeat property space keyword computes the
+ * repeat size which is image size plus spacing.
+ */
+ nsSize mRepeatSize;
+};
+
+struct nsCSSRendering {
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::Size Size;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::image::DrawResult DrawResult;
+ typedef nsIFrame::Sides Sides;
+
+ /**
+ * Initialize any static variables used by nsCSSRendering.
+ */
+ static void Init();
+
+ /**
+ * Clean up any static variables used by nsCSSRendering.
+ */
+ static void Shutdown();
+
+ static void PaintBoxShadowInner(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea);
+
+ static void PaintBoxShadowOuter(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aFrameArea,
+ const nsRect& aDirtyRect,
+ float aOpacity = 1.0);
+
+ static void ComputePixelRadii(const nscoord *aAppUnitsRadii,
+ nscoord aAppUnitsPerPixel,
+ RectCornerRadii *oBorderRadii);
+
+ /**
+ * Render the border for an element using css rendering rules
+ * for borders. aSkipSides says which sides to skip
+ * when rendering, the default is to skip none.
+ */
+ static DrawResult PaintBorder(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsStyleContext* aStyleContext,
+ mozilla::PaintBorderFlags aFlags,
+ Sides aSkipSides = Sides());
+
+ /**
+ * Like PaintBorder, but taking an nsStyleBorder argument instead of
+ * getting it from aStyleContext. aSkipSides says which sides to skip
+ * when rendering, the default is to skip none.
+ */
+ static DrawResult PaintBorderWithStyleBorder(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ const nsStyleBorder& aBorderStyle,
+ nsStyleContext* aStyleContext,
+ mozilla::PaintBorderFlags aFlags,
+ Sides aSkipSides = Sides());
+
+
+ /**
+ * Render the outline for an element using css rendering rules
+ * for borders.
+ */
+ static void PaintOutline(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsIFrame* aForFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsStyleContext* aStyleContext);
+
+ /**
+ * Render keyboard focus on an element.
+ * |aFocusRect| is the outer rectangle of the focused element.
+ * Uses a fixed style equivalent to "1px dotted |aColor|".
+ * Not used for controls, because the native theme may differ.
+ */
+ static void PaintFocus(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ const nsRect& aFocusRect,
+ nscolor aColor);
+
+ /**
+ * Render a gradient for an element.
+ * aDest is the rect for a single tile of the gradient on the destination.
+ * aFill is the rect on the destination to be covered by repeated tiling of
+ * the gradient.
+ * aSrc is the part of the gradient to be rendered into a tile (aDest), if
+ * aSrc and aDest are different sizes, the image will be scaled to map aSrc
+ * onto aDest.
+ * aIntrinsicSize is the size of the source gradient.
+ */
+ static void PaintGradient(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsStyleGradient* aGradient,
+ const nsRect& aDirtyRect,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsSize& aRepeatSize,
+ const mozilla::CSSIntRect& aSrc,
+ const nsSize& aIntrinsiceSize);
+
+ /**
+ * Find the frame whose background style should be used to draw the
+ * canvas background. aForFrame must be the frame for the root element
+ * whose background style should be used. This function will return
+ * aForFrame unless the <body> background should be propagated, in
+ * which case we return the frame associated with the <body>'s background.
+ */
+ static nsIFrame* FindBackgroundStyleFrame(nsIFrame* aForFrame);
+
+ /**
+ * @return true if |aFrame| is a canvas frame, in the CSS sense.
+ */
+ static bool IsCanvasFrame(nsIFrame* aFrame);
+
+ /**
+ * Fill in an aBackgroundSC to be used to paint the background
+ * for an element. This applies the rules for propagating
+ * backgrounds between BODY, the root element, and the canvas.
+ * @return true if there is some meaningful background.
+ */
+ static bool FindBackground(nsIFrame* aForFrame,
+ nsStyleContext** aBackgroundSC);
+
+ /**
+ * As FindBackground, but the passed-in frame is known to be a root frame
+ * (returned from nsCSSFrameConstructor::GetRootElementStyleFrame())
+ * and there is always some meaningful background returned.
+ */
+ static nsStyleContext* FindRootFrameBackground(nsIFrame* aForFrame);
+
+ /**
+ * Returns background style information for the canvas.
+ *
+ * @param aForFrame
+ * the frame used to represent the canvas, in the CSS sense (i.e.
+ * nsCSSRendering::IsCanvasFrame(aForFrame) must be true)
+ * @param aRootElementFrame
+ * the frame representing the root element of the document
+ * @param aBackground
+ * contains background style information for the canvas on return
+ */
+ static nsStyleContext*
+ FindCanvasBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame)
+ {
+ MOZ_ASSERT(IsCanvasFrame(aForFrame), "not a canvas frame");
+ if (aRootElementFrame)
+ return FindRootFrameBackground(aRootElementFrame);
+
+ // This should always give transparent, so we'll fill it in with the
+ // default color if needed. This seems to happen a bit while a page is
+ // being loaded.
+ return aForFrame->StyleContext();
+ }
+
+ /**
+ * Find a frame which draws a non-transparent background,
+ * for various table-related and HR-related backwards-compatibility hacks.
+ * This function will also stop if it finds themed frame which might draw
+ * background.
+ *
+ * Be very hesitant if you're considering calling this function -- it's
+ * usually not what you want.
+ */
+ static nsIFrame*
+ FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
+ bool aStartAtParent = false);
+
+ /**
+ * Determine the background color to draw taking into account print settings.
+ */
+ static nscolor
+ DetermineBackgroundColor(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ nsIFrame* aFrame,
+ bool& aDrawBackgroundImage,
+ bool& aDrawBackgroundColor);
+
+ static nsRect
+ ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame** aAttachedToFrame,
+ bool* aOutTransformedFixed);
+
+ static nsBackgroundLayerState
+ PrepareImageLayer(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ uint32_t aFlags,
+ const nsRect& aBorderArea,
+ const nsRect& aBGClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ bool* aOutIsTransformedFixed = nullptr);
+
+ struct ImageLayerClipState {
+ nsRect mBGClipArea; // Affected by mClippedRadii
+ nsRect mAdditionalBGClipArea; // Not affected by mClippedRadii
+ nsRect mDirtyRect;
+ gfxRect mDirtyRectGfx;
+
+ nscoord mRadii[8];
+ RectCornerRadii mClippedRadii;
+ bool mHasRoundedCorners;
+ bool mHasAdditionalBGClipArea;
+
+ // Whether we are being asked to draw with a caller provided background
+ // clipping area. If this is true we also disable rounded corners.
+ bool mCustomClip;
+ };
+
+ static void
+ GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
+ nsIFrame* aForFrame, const nsStyleBorder& aBorder,
+ const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
+ bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
+ /* out */ ImageLayerClipState* aClipState);
+
+ /**
+ * Render the background for an element using css rendering rules
+ * for backgrounds or mask.
+ */
+ enum {
+ /**
+ * When this flag is passed, the element's nsDisplayBorder will be
+ * painted immediately on top of this background.
+ */
+ PAINTBG_WILL_PAINT_BORDER = 0x01,
+ /**
+ * When this flag is passed, images are synchronously decoded.
+ */
+ PAINTBG_SYNC_DECODE_IMAGES = 0x02,
+ /**
+ * When this flag is passed, painting will go to the screen so we can
+ * take advantage of the fact that it will be clipped to the viewport.
+ */
+ PAINTBG_TO_WINDOW = 0x04,
+ /**
+ * When this flag is passed, painting will read properties of mask-image
+ * style, instead of background-image.
+ */
+ PAINTBG_MASK_IMAGE = 0x08
+ };
+
+ struct PaintBGParams {
+ nsPresContext& presCtx;
+ nsRenderingContext& renderingCtx;
+ nsRect dirtyRect;
+ nsRect borderArea;
+ nsIFrame* frame;
+ uint32_t paintFlags;
+ nsRect* bgClipRect = nullptr;
+ int32_t layer; // -1 means painting all layers; other
+ // value means painting one specific
+ // layer only.
+ CompositionOp compositionOp;
+
+ static PaintBGParams ForAllLayers(nsPresContext& aPresCtx,
+ nsRenderingContext& aRenderingCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame *aFrame,
+ uint32_t aPaintFlags);
+ static PaintBGParams ForSingleLayer(nsPresContext& aPresCtx,
+ nsRenderingContext& aRenderingCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame *aFrame,
+ uint32_t aPaintFlags,
+ int32_t aLayer,
+ CompositionOp aCompositionOp = CompositionOp::OP_OVER);
+
+ private:
+ PaintBGParams(nsPresContext& aPresCtx,
+ nsRenderingContext& aRenderingCtx,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsIFrame* aFrame,
+ uint32_t aPaintFlags,
+ int32_t aLayer,
+ CompositionOp aCompositionOp)
+ : presCtx(aPresCtx),
+ renderingCtx(aRenderingCtx),
+ dirtyRect(aDirtyRect),
+ borderArea(aBorderArea),
+ frame(aFrame),
+ paintFlags(aPaintFlags),
+ layer(aLayer),
+ compositionOp(aCompositionOp) {}
+ };
+
+ static DrawResult PaintBackground(const PaintBGParams& aParams);
+
+
+ /**
+ * Same as |PaintBackground|, except using the provided style structs.
+ * This short-circuits the code that ensures that the root element's
+ * background is drawn on the canvas.
+ * The aLayer parameter allows you to paint a single layer of the background.
+ * The default value for aLayer, -1, means that all layers will be painted.
+ * The background color will only be painted if the back-most layer is also
+ * being painted.
+ * aCompositionOp is only respected if a single layer is specified (aLayer != -1).
+ * If all layers are painted, the image layer's blend mode (or the mask
+ * layer's composition mode) will be used.
+ */
+ static DrawResult PaintBackgroundWithSC(const PaintBGParams& aParams,
+ nsStyleContext *mBackgroundSC,
+ const nsStyleBorder& aBorder);
+
+ /**
+ * Returns the rectangle covered by the given background layer image, taking
+ * into account background positioning, sizing, and repetition, but not
+ * clipping.
+ */
+ static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext,
+ nsIFrame* aForFrame,
+ const nsRect& aBorderArea,
+ const nsRect& aClipRect,
+ const nsStyleImageLayers::Layer& aLayer,
+ uint32_t aFlags);
+
+ /**
+ * Called when we start creating a display list. The frame tree will not
+ * change until a matching EndFrameTreeLocked is called.
+ */
+ static void BeginFrameTreesLocked();
+ /**
+ * Called when we've finished using a display list. When all
+ * BeginFrameTreeLocked calls have been balanced by an EndFrameTreeLocked,
+ * the frame tree may start changing again.
+ */
+ static void EndFrameTreesLocked();
+
+ // Draw a border segment in the table collapsing border model without
+ // beveling corners
+ static void DrawTableBorderSegment(DrawTarget& aDrawTarget,
+ uint8_t aBorderStyle,
+ nscolor aBorderColor,
+ const nsStyleBackground* aBGColor,
+ const nsRect& aBorderRect,
+ int32_t aAppUnitsPerDevPixel,
+ int32_t aAppUnitsPerCSSPixel,
+ uint8_t aStartBevelSide = 0,
+ nscoord aStartBevelOffset = 0,
+ uint8_t aEndBevelSide = 0,
+ nscoord aEndBevelOffset = 0);
+
+ // NOTE: pt, dirtyRect, lineSize, ascent, offset in the following
+ // structs are non-rounded device pixels, not app units.
+ struct DecorationRectParams
+ {
+ // The width [length] and the height [thickness] of the decoration
+ // line. This is a "logical" size in textRun orientation, so that
+ // for a vertical textrun, width will actually be a physical height;
+ // and conversely, height will be a physical width.
+ Size lineSize;
+ // The ascent of the text.
+ Float ascent = 0.0f;
+ // The offset of the decoration line from the baseline of the text
+ // (if the value is positive, the line is lifted up).
+ Float offset = 0.0f;
+ // If descentLimit is zero or larger and the underline overflows
+ // from the descent space, the underline should be lifted up as far
+ // as possible. Note that this does not mean the underline never
+ // overflows from this limitation, because if the underline is
+ // positioned to the baseline or upper, it causes unreadability.
+ // Note that if this is zero or larger, the underline rect may be
+ // shrunken if it's possible. Therefore, this value is used for
+ // strikeout line and overline too.
+ Float descentLimit = -1.0f;
+ // Which line will be painted. The value can be
+ // NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE or
+ // NS_STYLE_TEXT_DECORATION_LINE_OVERLINE or
+ // NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH.
+ uint8_t decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ // The style of the decoration line such as
+ // NS_STYLE_TEXT_DECORATION_STYLE_*.
+ uint8_t style = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ bool vertical = false;
+ };
+ struct PaintDecorationLineParams : DecorationRectParams
+ {
+ // No need to paint outside this rect.
+ Rect dirtyRect;
+ // The top/left edge of the text.
+ Point pt;
+ // The color of the decoration line.
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ // The distance between the left edge of the given frame and the
+ // position of the text as positioned without offset of the shadow.
+ Float icoordInFrame = 0.0f;
+ };
+
+ /**
+ * Function for painting the decoration lines for the text.
+ *
+ * input:
+ * @param aFrame the frame which needs the decoration line
+ * @param aGfxContext
+ */
+ static void PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const PaintDecorationLineParams& aParams);
+
+ /**
+ * Returns a Rect corresponding to the outline of the decoration line for the
+ * given text metrics. Arguments have the same meaning as for
+ * PaintDecorationLine. Currently this only works for solid
+ * decorations; for other decoration styles the returned Rect will be empty.
+ */
+ static Rect DecorationLineToPath(const PaintDecorationLineParams& aParams);
+
+ /**
+ * Function for getting the decoration line rect for the text.
+ * NOTE: aLineSize, aAscent and aOffset are non-rounded device pixels,
+ * not app units.
+ * input:
+ * @param aPresContext
+ * output:
+ * @return the decoration line rect for the input,
+ * the each values are app units.
+ */
+ static nsRect GetTextDecorationRect(nsPresContext* aPresContext,
+ const DecorationRectParams& aParams);
+
+ static CompositionOp GetGFXBlendMode(uint8_t mBlendMode) {
+ switch (mBlendMode) {
+ case NS_STYLE_BLEND_NORMAL: return CompositionOp::OP_OVER;
+ case NS_STYLE_BLEND_MULTIPLY: return CompositionOp::OP_MULTIPLY;
+ case NS_STYLE_BLEND_SCREEN: return CompositionOp::OP_SCREEN;
+ case NS_STYLE_BLEND_OVERLAY: return CompositionOp::OP_OVERLAY;
+ case NS_STYLE_BLEND_DARKEN: return CompositionOp::OP_DARKEN;
+ case NS_STYLE_BLEND_LIGHTEN: return CompositionOp::OP_LIGHTEN;
+ case NS_STYLE_BLEND_COLOR_DODGE: return CompositionOp::OP_COLOR_DODGE;
+ case NS_STYLE_BLEND_COLOR_BURN: return CompositionOp::OP_COLOR_BURN;
+ case NS_STYLE_BLEND_HARD_LIGHT: return CompositionOp::OP_HARD_LIGHT;
+ case NS_STYLE_BLEND_SOFT_LIGHT: return CompositionOp::OP_SOFT_LIGHT;
+ case NS_STYLE_BLEND_DIFFERENCE: return CompositionOp::OP_DIFFERENCE;
+ case NS_STYLE_BLEND_EXCLUSION: return CompositionOp::OP_EXCLUSION;
+ case NS_STYLE_BLEND_HUE: return CompositionOp::OP_HUE;
+ case NS_STYLE_BLEND_SATURATION: return CompositionOp::OP_SATURATION;
+ case NS_STYLE_BLEND_COLOR: return CompositionOp::OP_COLOR;
+ case NS_STYLE_BLEND_LUMINOSITY: return CompositionOp::OP_LUMINOSITY;
+ default: MOZ_ASSERT(false); return CompositionOp::OP_OVER;
+ }
+ }
+
+ static CompositionOp GetGFXCompositeMode(uint8_t aCompositeMode) {
+ switch (aCompositeMode) {
+ case NS_STYLE_MASK_COMPOSITE_ADD: return CompositionOp::OP_OVER;
+ case NS_STYLE_MASK_COMPOSITE_SUBTRACT: return CompositionOp::OP_OUT;
+ case NS_STYLE_MASK_COMPOSITE_INTERSECT: return CompositionOp::OP_IN;
+ case NS_STYLE_MASK_COMPOSITE_EXCLUDE: return CompositionOp::OP_XOR;
+ default: MOZ_ASSERT(false); return CompositionOp::OP_OVER;
+ }
+ }
+protected:
+ static gfxRect GetTextDecorationRectInternal(
+ const Point& aPt, const DecorationRectParams& aParams);
+
+ /**
+ * Returns inflated rect for painting a decoration line.
+ * Complex style decoration lines should be painted from leftmost of nearest
+ * ancestor block box because that makes better look of connection of lines
+ * for different nodes. ExpandPaintingRectForDecorationLine() returns
+ * a rect for actual painting rect for the clipped rect.
+ *
+ * input:
+ * @param aFrame the frame which needs the decoration line.
+ * @param aStyle the style of the complex decoration line
+ * NS_STYLE_TEXT_DECORATION_STYLE_DOTTED or
+ * NS_STYLE_TEXT_DECORATION_STYLE_DASHED or
+ * NS_STYLE_TEXT_DECORATION_STYLE_WAVY.
+ * @param aClippedRect the clipped rect for the decoration line.
+ * in other words, visible area of the line.
+ * @param aICoordInFrame the distance between inline-start edge of aFrame
+ * and aClippedRect.pos.
+ * @param aCycleLength the width of one cycle of the line style.
+ */
+ static Rect ExpandPaintingRectForDecorationLine(
+ nsIFrame* aFrame,
+ const uint8_t aStyle,
+ const Rect &aClippedRect,
+ const Float aICoordInFrame,
+ const Float aCycleLength,
+ bool aVertical);
+};
+
+/*
+ * nsContextBoxBlur
+ * Creates an 8-bit alpha channel context for callers to draw in, blurs the
+ * contents of that context and applies it as a 1-color mask on a
+ * different existing context. Uses gfxAlphaBoxBlur as its back end.
+ *
+ * You must call Init() first to create a suitable temporary surface to draw
+ * on. You must then draw any desired content onto the given context, then
+ * call DoPaint() to apply the blurred content as a single-color mask. You
+ * can only call Init() once, so objects cannot be reused.
+ *
+ * This is very useful for creating drop shadows or silhouettes.
+ */
+class nsContextBoxBlur {
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+public:
+ enum {
+ FORCE_MASK = 0x01
+ };
+ /**
+ * Prepares a gfxContext to draw on. Do not call this twice; if you want
+ * to get the gfxContext again use GetContext().
+ *
+ * @param aRect The coordinates of the surface to create.
+ * All coordinates must be in app units.
+ * This must not include the blur radius, pass
+ * it as the second parameter and everything
+ * is taken care of.
+ *
+ * @param aBlurRadius The blur radius in app units.
+ *
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ *
+ * @param aDestinationCtx The graphics context to apply the blurred
+ * mask to when you call DoPaint(). Make sure
+ * it is not destroyed before you call
+ * DoPaint(). To set the color of the
+ * resulting blurred graphic mask, you must
+ * set the color on this context before
+ * calling Init().
+ *
+ * @param aDirtyRect The absolute dirty rect in app units. Used to
+ * optimize the temporary surface size and speed up blur.
+ *
+ * @param aSkipRect An area in device pixels (NOT app units!) to avoid
+ * blurring over, to prevent unnecessary work.
+ *
+ * @param aFlags FORCE_MASK to ensure that the content drawn to the
+ * returned gfxContext is used as a mask, and not
+ * drawn directly to aDestinationCtx.
+ *
+ * @return A blank 8-bit alpha-channel-only graphics context to
+ * draw on, or null on error. Must not be freed. The
+ * context has a device offset applied to it given by
+ * aRect. This means you can use coordinates as if it
+ * were at the desired position at aRect and you don't
+ * need to worry about translating any coordinates to
+ * draw on this temporary surface.
+ *
+ * If aBlurRadius is 0, the returned context is aDestinationCtx and
+ * DoPaint() does nothing, because no blurring is required. Therefore, you
+ * should prepare the destination context as if you were going to draw
+ * directly on it instead of any temporary surface created in this class.
+ */
+ gfxContext* Init(const nsRect& aRect, nscoord aSpreadRadius,
+ nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel, gfxContext* aDestinationCtx,
+ const nsRect& aDirtyRect, const gfxRect* aSkipRect,
+ uint32_t aFlags = 0);
+
+ /**
+ * Does the actual blurring and mask applying. Users of this object *must*
+ * have called Init() first, then have drawn whatever they want to be
+ * blurred onto the internal gfxContext before calling this.
+ */
+ void DoPaint();
+
+ /**
+ * Gets the internal gfxContext at any time. Must not be freed. Avoid
+ * calling this before calling Init() since the context would not be
+ * constructed at that point.
+ */
+ gfxContext* GetContext();
+
+
+ /**
+ * Get the margin associated with the given blur radius, i.e., the
+ * additional area that might be painted as a result of it. (The
+ * margin for a spread radius is itself, on all sides.)
+ */
+ static nsMargin GetBlurRadiusMargin(nscoord aBlurRadius,
+ int32_t aAppUnitsPerDevPixel);
+
+ /**
+ * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent
+ * to calling Init(), drawing a rectangle onto the returned surface
+ * and then calling DoPaint, but may let us optimize better in the
+ * backend.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aRect The rectangle to blur in app units.
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ * @param aCornerRadii Corner radii for aRect, if it is a rounded
+ * rectangle.
+ * @param aBlurRadius The blur radius in app units.
+ * @param aShadowColor The color to draw the blurred shadow.
+ * @param aDirtyRect The absolute dirty rect in app units. Used to
+ * optimize the temporary surface size and speed up blur.
+ * @param aSkipRect An area in device pixels (NOT app units!) to avoid
+ * blurring over, to prevent unnecessary work.
+ */
+ static void BlurRectangle(gfxContext* aDestinationCtx,
+ const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel,
+ RectCornerRadii* aCornerRadii,
+ nscoord aBlurRadius,
+ const Color& aShadowColor,
+ const nsRect& aDirtyRect,
+ const gfxRect& aSkipRect);
+
+ /**
+ * Draws a blurred inset box shadow shape onto the destination surface.
+ * Like BlurRectangle, this is equivalent to calling Init(),
+ * drawing a rectangle onto the returned surface
+ * and then calling DoPaint, but may let us optimize better in the
+ * backend.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aDestinationRect The rectangle to blur in app units.
+ * @param aShadowClipRect The inside clip rect that creates the path.
+ * @param aShadowColor The color of the blur
+ * @param aBlurRadiusAppUnits The blur radius in app units
+ * @param aSpreadRadiusAppUnits The spread radius in app units.
+ * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+ * for conversion. Most of the time you'll
+ * pass this from the current PresContext if
+ * available.
+ * @param aHasBorderRadius If this inset box blur has a border radius
+ * @param aInnerClipRectRadii The clip rect radii used for the inside rect's path.
+ * @param aSkipRect An area in device pixels (NOT app units!) to avoid
+ * blurring over, to prevent unnecessary work.
+ */
+ bool InsetBoxBlur(gfxContext* aDestinationCtx,
+ mozilla::gfx::Rect aDestinationRect,
+ mozilla::gfx::Rect aShadowClipRect,
+ mozilla::gfx::Color& aShadowColor,
+ nscoord aBlurRadiusAppUnits,
+ nscoord aSpreadRadiusAppUnits,
+ int32_t aAppUnitsPerDevPixel,
+ bool aHasBorderRadius,
+ RectCornerRadii& aInnerClipRectRadii,
+ mozilla::gfx::Rect aSkipRect,
+ mozilla::gfx::Point aShadowOffset);
+
+protected:
+ static void GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ nscoord aBlurRadius,
+ nscoord aSpreadRadius,
+ mozilla::gfx::IntSize& aOutBlurRadius,
+ mozilla::gfx::IntSize& aOutSpreadRadius,
+ bool aConstrainSpreadRadius = true);
+
+ gfxAlphaBoxBlur mAlphaBoxBlur;
+ RefPtr<gfxContext> mContext;
+ gfxContext* mDestinationCtx;
+
+ /* This is true if the blur already has it's content transformed
+ * by mDestinationCtx's transform */
+ bool mPreTransformed;
+};
+
+#endif /* nsCSSRendering_h___ */
diff --git a/layout/base/nsCSSRenderingBorders.cpp b/layout/base/nsCSSRenderingBorders.cpp
new file mode 100644
index 000000000..e6ad25c97
--- /dev/null
+++ b/layout/base/nsCSSRenderingBorders.cpp
@@ -0,0 +1,3532 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCSSRenderingBorders.h"
+
+#include "gfxUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "BorderConsts.h"
+#include "DashedCornerFinder.h"
+#include "DottedCornerFinder.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsContentUtils.h"
+#include "nsCSSColorUtils.h"
+#include "GeckoProfiler.h"
+#include "nsExpirationTracker.h"
+#include "RoundedRect.h"
+#include "nsIScriptError.h"
+#include "nsClassHashtable.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+#include "mozilla/gfx/2D.h"
+#include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
+
+/**
+ * nsCSSRendering::PaintBorder
+ * nsCSSRendering::PaintOutline
+ * -> DrawBorders
+ *
+ * DrawBorders
+ * -> Ability to use specialized approach?
+ * |- Draw using specialized function
+ * |- separate corners?
+ * |- dashed side mask
+ * |
+ * -> can border be drawn in 1 pass? (e.g., solid border same color all around)
+ * |- DrawBorderSides with all 4 sides
+ * -> more than 1 pass?
+ * |- for each corner
+ * |- clip to DoCornerClipSubPath
+ * |- for each side adjacent to corner
+ * |- clip to GetSideClipSubPath
+ * |- DrawBorderSides with one side
+ * |- for each side
+ * |- GetSideClipWithoutCornersRect
+ * |- DrawDashedOrDottedSide || DrawBorderSides with one side
+ */
+
+static void ComputeBorderCornerDimensions(const Float* aBorderWidths,
+ const RectCornerRadii& aRadii,
+ RectCornerRadii *aDimsResult);
+
+// given a side index, get the previous and next side index
+#define NEXT_SIDE(_s) mozilla::css::Side(((_s) + 1) & 3)
+#define PREV_SIDE(_s) mozilla::css::Side(((_s) + 3) & 3)
+
+// given a corner index, get the previous and next corner index
+#define NEXT_CORNER(_s) mozilla::css::Corner(((_s) + 1) & 3)
+#define PREV_CORNER(_s) mozilla::css::Corner(((_s) + 3) & 3)
+
+// from the given base color and the background color, turn
+// color into a color for the given border pattern style
+static Color MakeBorderColor(nscolor aColor,
+ nscolor aBackgroundColor,
+ BorderColorStyle aBorderColorStyle);
+
+
+// Given a line index (an index starting from the outside of the
+// border going inwards) and an array of line styles, calculate the
+// color that that stripe of the border should be rendered in.
+static Color ComputeColorForLine(uint32_t aLineIndex,
+ const BorderColorStyle* aBorderColorStyle,
+ uint32_t aBorderColorStyleCount,
+ nscolor aBorderColor,
+ nscolor aBackgroundColor);
+
+static Color ComputeCompositeColorForLine(uint32_t aLineIndex,
+ const nsBorderColors* aBorderColors);
+
+// little helper function to check if the array of 4 floats given are
+// equal to the given value
+static bool
+CheckFourFloatsEqual(const Float *vals, Float k)
+{
+ return (vals[0] == k &&
+ vals[1] == k &&
+ vals[2] == k &&
+ vals[3] == k);
+}
+
+static bool
+IsZeroSize(const Size& sz) {
+ return sz.width == 0.0 || sz.height == 0.0;
+}
+
+static bool
+AllCornersZeroSize(const RectCornerRadii& corners) {
+ return IsZeroSize(corners[NS_CORNER_TOP_LEFT]) &&
+ IsZeroSize(corners[NS_CORNER_TOP_RIGHT]) &&
+ IsZeroSize(corners[NS_CORNER_BOTTOM_RIGHT]) &&
+ IsZeroSize(corners[NS_CORNER_BOTTOM_LEFT]);
+}
+
+static mozilla::css::Side
+GetHorizontalSide(mozilla::css::Corner aCorner)
+{
+ return (aCorner == C_TL || aCorner == C_TR) ? NS_SIDE_TOP : NS_SIDE_BOTTOM;
+}
+
+static mozilla::css::Side
+GetVerticalSide(mozilla::css::Corner aCorner)
+{
+ return (aCorner == C_TL || aCorner == C_BL) ? NS_SIDE_LEFT : NS_SIDE_RIGHT;
+}
+
+static mozilla::css::Corner
+GetCWCorner(mozilla::css::Side aSide)
+{
+ return mozilla::css::Corner(NEXT_SIDE(aSide));
+}
+
+static mozilla::css::Corner
+GetCCWCorner(mozilla::css::Side aSide)
+{
+ return mozilla::css::Corner(aSide);
+}
+
+static bool
+IsSingleSide(int aSides)
+{
+ return aSides == SIDE_BIT_TOP || aSides == SIDE_BIT_RIGHT ||
+ aSides == SIDE_BIT_BOTTOM || aSides == SIDE_BIT_LEFT;
+}
+
+static bool
+IsHorizontalSide(mozilla::css::Side aSide)
+{
+ return aSide == NS_SIDE_TOP || aSide == NS_SIDE_BOTTOM;
+}
+
+typedef enum {
+ // Normal solid square corner. Will be rectangular, the size of the
+ // adjacent sides. If the corner has a border radius, the corner
+ // will always be solid, since we don't do dotted/dashed etc.
+ CORNER_NORMAL,
+
+ // Paint the corner in whatever style is not dotted/dashed of the
+ // adjacent corners.
+ CORNER_SOLID,
+
+ // Paint the corner as a dot, the size of the bigger of the adjacent
+ // sides.
+ CORNER_DOT
+} CornerStyle;
+
+nsCSSBorderRenderer::nsCSSBorderRenderer(nsPresContext* aPresContext,
+ const nsIDocument* aDocument,
+ DrawTarget* aDrawTarget,
+ const Rect& aDirtyRect,
+ Rect& aOuterRect,
+ const uint8_t* aBorderStyles,
+ const Float* aBorderWidths,
+ RectCornerRadii& aBorderRadii,
+ const nscolor* aBorderColors,
+ nsBorderColors* const* aCompositeColors,
+ nscolor aBackgroundColor)
+ : mPresContext(aPresContext),
+ mDocument(aDocument),
+ mDrawTarget(aDrawTarget),
+ mDirtyRect(aDirtyRect),
+ mOuterRect(aOuterRect),
+ mBorderStyles(aBorderStyles),
+ mBorderWidths(aBorderWidths),
+ mBorderRadii(aBorderRadii),
+ mBorderColors(aBorderColors),
+ mCompositeColors(aCompositeColors),
+ mBackgroundColor(aBackgroundColor)
+{
+ if (!mCompositeColors) {
+ static nsBorderColors * const noColors[4] = { nullptr };
+ mCompositeColors = &noColors[0];
+ }
+
+ mInnerRect = mOuterRect;
+ mInnerRect.Deflate(
+ Margin(mBorderStyles[0] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[0] : 0,
+ mBorderStyles[1] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[1] : 0,
+ mBorderStyles[2] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[2] : 0,
+ mBorderStyles[3] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[3] : 0));
+
+ ComputeBorderCornerDimensions(mBorderWidths,
+ mBorderRadii, &mBorderCornerDimensions);
+
+ mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0);
+ mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
+ mAvoidStroke = false;
+}
+
+/* static */ void
+nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aInnerRadiiRet)
+{
+ RectCornerRadii& iRadii = *aInnerRadiiRet;
+
+ iRadii[C_TL].width = std::max(0.f, aRadii[C_TL].width - aBorderSizes[NS_SIDE_LEFT]);
+ iRadii[C_TL].height = std::max(0.f, aRadii[C_TL].height - aBorderSizes[NS_SIDE_TOP]);
+
+ iRadii[C_TR].width = std::max(0.f, aRadii[C_TR].width - aBorderSizes[NS_SIDE_RIGHT]);
+ iRadii[C_TR].height = std::max(0.f, aRadii[C_TR].height - aBorderSizes[NS_SIDE_TOP]);
+
+ iRadii[C_BR].width = std::max(0.f, aRadii[C_BR].width - aBorderSizes[NS_SIDE_RIGHT]);
+ iRadii[C_BR].height = std::max(0.f, aRadii[C_BR].height - aBorderSizes[NS_SIDE_BOTTOM]);
+
+ iRadii[C_BL].width = std::max(0.f, aRadii[C_BL].width - aBorderSizes[NS_SIDE_LEFT]);
+ iRadii[C_BL].height = std::max(0.f, aRadii[C_BL].height - aBorderSizes[NS_SIDE_BOTTOM]);
+}
+
+/* static */ void
+nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aOuterRadiiRet)
+{
+ RectCornerRadii& oRadii = *aOuterRadiiRet;
+
+ // default all corners to sharp corners
+ oRadii = RectCornerRadii(0.f);
+
+ // round the edges that have radii > 0.0 to start with
+ if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) {
+ oRadii[C_TL].width = std::max(0.f, aRadii[C_TL].width + aBorderSizes[NS_SIDE_LEFT]);
+ oRadii[C_TL].height = std::max(0.f, aRadii[C_TL].height + aBorderSizes[NS_SIDE_TOP]);
+ }
+
+ if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) {
+ oRadii[C_TR].width = std::max(0.f, aRadii[C_TR].width + aBorderSizes[NS_SIDE_RIGHT]);
+ oRadii[C_TR].height = std::max(0.f, aRadii[C_TR].height + aBorderSizes[NS_SIDE_TOP]);
+ }
+
+ if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) {
+ oRadii[C_BR].width = std::max(0.f, aRadii[C_BR].width + aBorderSizes[NS_SIDE_RIGHT]);
+ oRadii[C_BR].height = std::max(0.f, aRadii[C_BR].height + aBorderSizes[NS_SIDE_BOTTOM]);
+ }
+
+ if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) {
+ oRadii[C_BL].width = std::max(0.f, aRadii[C_BL].width + aBorderSizes[NS_SIDE_LEFT]);
+ oRadii[C_BL].height = std::max(0.f, aRadii[C_BL].height + aBorderSizes[NS_SIDE_BOTTOM]);
+ }
+}
+
+/*static*/ void
+ComputeBorderCornerDimensions(const Float* aBorderWidths,
+ const RectCornerRadii& aRadii,
+ RectCornerRadii* aDimsRet)
+{
+ Float leftWidth = aBorderWidths[NS_SIDE_LEFT];
+ Float topWidth = aBorderWidths[NS_SIDE_TOP];
+ Float rightWidth = aBorderWidths[NS_SIDE_RIGHT];
+ Float bottomWidth = aBorderWidths[NS_SIDE_BOTTOM];
+
+ if (AllCornersZeroSize(aRadii)) {
+ // These will always be in pixel units from CSS
+ (*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
+ (*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
+ (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
+ (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
+ } else {
+ // Always round up to whole pixels for the corners; it's safe to
+ // make the corners bigger than necessary, and this way we ensure
+ // that we avoid seams.
+ (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
+ ceil(std::max(topWidth, aRadii[C_TL].height)));
+ (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
+ ceil(std::max(topWidth, aRadii[C_TR].height)));
+ (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
+ ceil(std::max(bottomWidth, aRadii[C_BR].height)));
+ (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
+ ceil(std::max(bottomWidth, aRadii[C_BL].height)));
+ }
+}
+
+bool
+nsCSSBorderRenderer::AreBorderSideFinalStylesSame(uint8_t aSides)
+{
+ NS_ASSERTION(aSides != 0 && (aSides & ~SIDE_BITS_ALL) == 0,
+ "AreBorderSidesSame: invalid whichSides!");
+
+ /* First check if the specified styles and colors are the same for all sides */
+ int firstStyle = 0;
+ NS_FOR_CSS_SIDES (i) {
+ if (firstStyle == i) {
+ if (((1 << i) & aSides) == 0)
+ firstStyle++;
+ continue;
+ }
+
+ if (((1 << i) & aSides) == 0) {
+ continue;
+ }
+
+ if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
+ mBorderColors[firstStyle] != mBorderColors[i] ||
+ !nsBorderColors::Equal(mCompositeColors[firstStyle],
+ mCompositeColors[i]))
+ return false;
+ }
+
+ /* Then if it's one of the two-tone styles and we're not
+ * just comparing the TL or BR sides */
+ switch (mBorderStyles[firstStyle]) {
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ case NS_STYLE_BORDER_STYLE_INSET:
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ return ((aSides & ~(SIDE_BIT_TOP | SIDE_BIT_LEFT)) == 0 ||
+ (aSides & ~(SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT)) == 0);
+ }
+
+ return true;
+}
+
+bool
+nsCSSBorderRenderer::IsSolidCornerStyle(uint8_t aStyle, mozilla::css::Corner aCorner)
+{
+ switch (aStyle) {
+ case NS_STYLE_BORDER_STYLE_SOLID:
+ return true;
+
+ case NS_STYLE_BORDER_STYLE_INSET:
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ return (aCorner == NS_CORNER_TOP_LEFT || aCorner == NS_CORNER_BOTTOM_RIGHT);
+
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ return mOneUnitBorder && (aCorner == NS_CORNER_TOP_LEFT || aCorner == NS_CORNER_BOTTOM_RIGHT);
+
+ case NS_STYLE_BORDER_STYLE_DOUBLE:
+ return mOneUnitBorder;
+
+ default:
+ return false;
+ }
+}
+
+bool
+nsCSSBorderRenderer::IsCornerMergeable(mozilla::css::Corner aCorner)
+{
+ // Corner between dotted borders with same width and small radii is
+ // merged into single dot.
+ //
+ // widthH / 2.0
+ // |<---------->|
+ // | |
+ // |radius.width|
+ // |<--->| |
+ // | | |
+ // | _+------+------------+-----
+ // | / ###|### |
+ // |/ #######|####### |
+ // + #########|######### |
+ // | ##########|########## |
+ // | ###########|########### |
+ // | ###########|########### |
+ // |############|############|
+ // +------------+############|
+ // |#########################|
+ // | ####################### |
+ // | ####################### |
+ // | ##################### |
+ // | ################### |
+ // | ############### |
+ // | ####### |
+ // +-------------------------+----
+ // | |
+ // | |
+ mozilla::css::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::css::Side sideV(GetVerticalSide(aCorner));
+ uint8_t styleH = mBorderStyles[sideH];
+ uint8_t styleV = mBorderStyles[sideV];
+ if (styleH != styleV || styleH != NS_STYLE_BORDER_STYLE_DOTTED) {
+ return false;
+ }
+
+ Float widthH = mBorderWidths[sideH];
+ Float widthV = mBorderWidths[sideV];
+ if (widthH != widthV) {
+ return false;
+ }
+
+ Size radius = mBorderRadii[aCorner];
+ return IsZeroSize(radius) ||
+ (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
+}
+
+BorderColorStyle
+nsCSSBorderRenderer::BorderColorStyleForSolidCorner(uint8_t aStyle, mozilla::css::Corner aCorner)
+{
+ // note that this function assumes that the corner is already solid,
+ // as per the earlier function
+ switch (aStyle) {
+ case NS_STYLE_BORDER_STYLE_SOLID:
+ case NS_STYLE_BORDER_STYLE_DOUBLE:
+ return BorderColorStyleSolid;
+
+ case NS_STYLE_BORDER_STYLE_INSET:
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ if (aCorner == NS_CORNER_TOP_LEFT)
+ return BorderColorStyleDark;
+ else if (aCorner == NS_CORNER_BOTTOM_RIGHT)
+ return BorderColorStyleLight;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ if (aCorner == NS_CORNER_TOP_LEFT)
+ return BorderColorStyleLight;
+ else if (aCorner == NS_CORNER_BOTTOM_RIGHT)
+ return BorderColorStyleDark;
+ break;
+ }
+
+ return BorderColorStyleNone;
+}
+
+Rect
+nsCSSBorderRenderer::GetCornerRect(mozilla::css::Corner aCorner)
+{
+ Point offset(0.f, 0.f);
+
+ if (aCorner == C_TR || aCorner == C_BR)
+ offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
+ if (aCorner == C_BR || aCorner == C_BL)
+ offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
+
+ return Rect(mOuterRect.TopLeft() + offset,
+ mBorderCornerDimensions[aCorner]);
+}
+
+Rect
+nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::css::Side aSide)
+{
+ Point offset(0.f, 0.f);
+
+ // The offset from the outside rect to the start of this side's
+ // box. For the top and bottom sides, the height of the box
+ // must be the border height; the x start must take into account
+ // the corner size (which may be bigger than the right or left
+ // side's width). The same applies to the right and left sides.
+ if (aSide == NS_SIDE_TOP) {
+ offset.x = mBorderCornerDimensions[C_TL].width;
+ } else if (aSide == NS_SIDE_RIGHT) {
+ offset.x = mOuterRect.Width() - mBorderWidths[NS_SIDE_RIGHT];
+ offset.y = mBorderCornerDimensions[C_TR].height;
+ } else if (aSide == NS_SIDE_BOTTOM) {
+ offset.x = mBorderCornerDimensions[C_BL].width;
+ offset.y = mOuterRect.Height() - mBorderWidths[NS_SIDE_BOTTOM];
+ } else if (aSide == NS_SIDE_LEFT) {
+ offset.y = mBorderCornerDimensions[C_TL].height;
+ }
+
+ // The sum of the width & height of the corners adjacent to the
+ // side. This relies on the relationship between side indexing and
+ // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
+ // with both proceeding clockwise.
+ Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)]
+ + mBorderCornerDimensions[GetCWCorner(aSide)];
+ Rect rect(mOuterRect.TopLeft() + offset,
+ mOuterRect.Size() - sideCornerSum);
+
+ if (IsHorizontalSide(aSide))
+ rect.height = mBorderWidths[aSide];
+ else
+ rect.width = mBorderWidths[aSide];
+
+ return rect;
+}
+
+// The side border type and the adjacent border types are
+// examined and one of the different types of clipping (listed
+// below) is selected.
+
+typedef enum {
+ // clip to the trapezoid formed by the corners of the
+ // inner and outer rectangles for the given side
+ //
+ // +---------------
+ // |\%%%%%%%%%%%%%%
+ // | \%%%%%%%%%%%%
+ // | \%%%%%%%%%%%
+ // | \%%%%%%%%%
+ // | +--------
+ // | |
+ // | |
+ SIDE_CLIP_TRAPEZOID,
+
+ // clip to the trapezoid formed by the outer rectangle
+ // corners and the center of the region, making sure
+ // that diagonal lines all go directly from the outside
+ // corner to the inside corner, but that they then continue on
+ // to the middle.
+ //
+ // This is needed for correctly clipping rounded borders,
+ // which might extend past the SIDE_CLIP_TRAPEZOID trap.
+ //
+ // +-------__--+---
+ // \%%%%_-%%%%%%%%
+ // \+-%%%%%%%%%%
+ // / \%%%%%%%%%%
+ // / \%%%%%%%%%
+ // | +%%_-+---
+ // | +%%%%%%
+ // | / \%%%%%
+ // + + \%%%
+ // | | +-
+ SIDE_CLIP_TRAPEZOID_FULL,
+
+ // clip to the rectangle formed by the given side including corner.
+ // This is used by the non-dotted side next to dotted side.
+ //
+ // +---------------
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // |%%%%%%%%%%%%%%%
+ // +------+--------
+ // | |
+ // | |
+ SIDE_CLIP_RECTANGLE_CORNER,
+
+ // clip to the rectangle formed by the given side excluding corner.
+ // This is used by the dotted side next to non-dotted side.
+ //
+ // +------+--------
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | |%%%%%%%%
+ // | +--------
+ // | |
+ // | |
+ SIDE_CLIP_RECTANGLE_NO_CORNER,
+} SideClipType;
+
+// Given three points, p0, p1, and midPoint, move p1 further in to the
+// rectangle (of which aMidPoint is the center) so that it reaches the
+// closer of the horizontal or vertical lines intersecting the midpoint,
+// while maintaing the slope of the line. If p0 and p1 are the same,
+// just move p1 to midPoint (since there's no slope to maintain).
+// FIXME: Extending only to the midpoint isn't actually sufficient for
+// boxes with asymmetric radii.
+static void
+MaybeMoveToMidPoint(Point& aP0, Point& aP1, const Point& aMidPoint)
+{
+ Point ps = aP1 - aP0;
+
+ if (ps.x == 0.0) {
+ if (ps.y == 0.0) {
+ aP1 = aMidPoint;
+ } else {
+ aP1.y = aMidPoint.y;
+ }
+ } else {
+ if (ps.y == 0.0) {
+ aP1.x = aMidPoint.x;
+ } else {
+ Float k = std::min((aMidPoint.x - aP0.x) / ps.x,
+ (aMidPoint.y - aP0.y) / ps.y);
+ aP1 = aP0 + ps * k;
+ }
+ }
+}
+
+already_AddRefed<Path>
+nsCSSBorderRenderer::GetSideClipSubPath(mozilla::css::Side aSide)
+{
+ // the clip proceeds clockwise from the top left corner;
+ // so "start" in each case is the start of the region from that side.
+ //
+ // the final path will be formed like:
+ // s0 ------- e0
+ // | /
+ // s1 ----- e1
+ //
+ // that is, the second point will always be on the inside
+
+ Point start[2];
+ Point end[2];
+
+#define IS_DOTTED(_s) ((_s) == NS_STYLE_BORDER_STYLE_DOTTED)
+ bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
+ bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
+ bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
+#undef IS_DOTTED
+
+ SideClipType startType = SIDE_CLIP_TRAPEZOID;
+ SideClipType endType = SIDE_CLIP_TRAPEZOID;
+
+ if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
+ startType = SIDE_CLIP_TRAPEZOID_FULL;
+ } else if (startIsDotted && !isDotted) {
+ startType = SIDE_CLIP_RECTANGLE_CORNER;
+ } else if (!startIsDotted && isDotted) {
+ startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
+ }
+
+ if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
+ endType = SIDE_CLIP_TRAPEZOID_FULL;
+ } else if (endIsDotted && !isDotted) {
+ endType = SIDE_CLIP_RECTANGLE_CORNER;
+ } else if (!endIsDotted && isDotted) {
+ endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
+ }
+
+ Point midPoint = mInnerRect.Center();
+
+ start[0] = mOuterRect.CCWCorner(aSide);
+ start[1] = mInnerRect.CCWCorner(aSide);
+
+ end[0] = mOuterRect.CWCorner(aSide);
+ end[1] = mInnerRect.CWCorner(aSide);
+
+ if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
+ MaybeMoveToMidPoint(start[0], start[1], midPoint);
+ } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ start[1] = Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
+ } else {
+ start[1] = Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
+ }
+ } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ start[0] = Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
+ } else {
+ start[0] = Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
+ }
+ }
+
+ if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
+ MaybeMoveToMidPoint(end[0], end[1], midPoint);
+ } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ end[1] = Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
+ } else {
+ end[1] = Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
+ }
+ } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
+ if (IsHorizontalSide(aSide)) {
+ end[0] = Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
+ } else {
+ end[0] = Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
+ }
+ }
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(start[0]);
+ builder->LineTo(end[0]);
+ builder->LineTo(end[1]);
+ builder->LineTo(start[1]);
+ builder->Close();
+ return builder->Finish();
+}
+
+Point
+nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner,
+ bool* aIsUnfilled,
+ Float aDotOffset)
+
+{
+ // Calculate the end point of the side for dashed/dotted border, that is also
+ // the end point of the corner curve. The point is specified by aSide and
+ // aCorner. (e.g. NS_SIDE_TOP and C_TL means the left end of border-top)
+ //
+ //
+ // aCorner aSide
+ // +--------------------
+ // |
+ // |
+ // | +----------
+ // | the end point
+ // |
+ // | +----------
+ // | |
+ // | |
+ // | |
+ //
+ // The position of the point depends on the border-style, border-width, and
+ // border-radius of the side, corner, and the adjacent side beyond the corner,
+ // to make those sides (and corner) interact well.
+ //
+ // If the style of aSide is dotted and the dot at the point should be
+ // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
+
+ const Float signsList[4][2] = {
+ { +1.0f, +1.0f },
+ { -1.0f, +1.0f },
+ { -1.0f, -1.0f },
+ { +1.0f, -1.0f }
+ };
+ const Float (& signs)[2] = signsList[aCorner];
+
+ *aIsUnfilled = false;
+
+ Point P = mOuterRect.AtCorner(aCorner);
+ uint8_t style = mBorderStyles[aSide];
+ Float borderWidth = mBorderWidths[aSide];
+ Size dim = mBorderCornerDimensions[aCorner];
+ bool isHorizontal = IsHorizontalSide(aSide);
+ //
+ // aCorner aSide
+ // +--------------
+ // |
+ // | +----------
+ // | |
+ // otherSide | |
+ // | |
+ mozilla::css::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
+ ? PREV_SIDE(aSide)
+ : NEXT_SIDE(aSide);
+ uint8_t otherStyle = mBorderStyles[otherSide];
+ Float otherBorderWidth = mBorderWidths[otherSide];
+ Size radius = mBorderRadii[aCorner];
+ if (IsZeroSize(radius)) {
+ radius.width = 0.0f;
+ radius.height = 0.0f;
+ }
+ if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
+ // Offset the dot's location along the side toward the corner by a
+ // multiple of its width.
+ if (isHorizontal) {
+ P.x -= signs[0] * aDotOffset * borderWidth;
+ } else {
+ P.y -= signs[1] * aDotOffset * borderWidth;
+ }
+ }
+ if (style == NS_STYLE_BORDER_STYLE_DOTTED &&
+ otherStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
+ if (borderWidth == otherBorderWidth) {
+ if (radius.width < borderWidth / 2.0f &&
+ radius.height < borderWidth / 2.0f) {
+ // Two dots are merged into one and placed at the corner.
+ //
+ // borderWidth / 2.0
+ // |<---------->|
+ // | |
+ // |radius.width|
+ // |<--->| |
+ // | | |
+ // | _+------+------------+-----
+ // | / ###|### |
+ // |/ #######|####### |
+ // + #########|######### |
+ // | ##########|########## |
+ // | ###########|########### |
+ // | ###########|########### |
+ // |############|############|
+ // +------------+############|
+ // |########### P ###########|
+ // | ####################### |
+ // | ####################### |
+ // | ##################### |
+ // | ################### |
+ // | ############### |
+ // | ####### |
+ // +-------------------------+----
+ // | |
+ // | |
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ // Two dots are drawn separately.
+ //
+ // borderWidth * 1.5
+ // |<------------>|
+ // | |
+ // |radius.width |
+ // |<----->| |
+ // | | |
+ // | _--+-+----+---
+ // | _- | ##|##
+ // | / | ###|###
+ // |/ |####|####
+ // | |####+####
+ // | |### P ###
+ // + | ###|###
+ // | | ##|##
+ // +---------+----+---
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ //
+ // There should be enough gap between 2 dots even if radius.width is
+ // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the
+ // value that there's imaginally unfilled dot at the corner. The
+ // unfilled dot may overflow from the outer curve, but filled dots
+ // doesn't, so this could be acceptable solution at least for now.
+ // We may have to find better model/value.
+ //
+ // imaginally unfilled dot at the corner
+ // |
+ // v +----+---
+ // ***** | ##|##
+ // ******* | ###|###
+ // *********|####|####
+ // *********|####+####
+ // *********|### P ###
+ // ******* | ###|###
+ // ***** | ##|##
+ // +---------+----+---
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ // | |
+ Float minimum = borderWidth * 1.5f;
+ if (isHorizontal) {
+ P.x += signs[0] * std::max(radius.width, minimum);
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * std::max(radius.height, minimum);
+ }
+ }
+
+ return P;
+ }
+
+ if (borderWidth < otherBorderWidth) {
+ // This side is smaller than other side, other side draws the corner.
+ //
+ // otherBorderWidth + borderWidth / 2.0
+ // |<---------->|
+ // | |
+ // +---------+--+--------
+ // | ##### | *|* ###
+ // | ####### |**|**#####
+ // |#########|**+**##+##
+ // |####+####|* P *#####
+ // |#########| *** ###
+ // | ####### +-----------
+ // | ##### | ^
+ // | | |
+ // | | first dot is not filled
+ // | |
+ //
+ // radius.width
+ // |<----------------->|
+ // | |
+ // | ___---+-------------
+ // | __-- #|# ###
+ // | _- ##|## #####
+ // | / ##+## ##+##
+ // | / # P # #####
+ // | | #|# ###
+ // | | __--+-------------
+ // || _- ^
+ // || / |
+ // | / first dot is filled
+ // | |
+ // | |
+ // | ##### |
+ // | ####### |
+ // |#########|
+ // +----+----+
+ // |#########|
+ // | ####### |
+ // | ##### |
+ Float minimum = otherBorderWidth + borderWidth / 2.0f;
+ if (isHorizontal) {
+ if (radius.width < minimum) {
+ *aIsUnfilled = true;
+ P.x += signs[0] * minimum;
+ } else {
+ P.x += signs[0] * radius.width;
+ }
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ if (radius.height < minimum) {
+ *aIsUnfilled = true;
+ P.y += signs[1] * minimum;
+ } else {
+ P.y += signs[1] * radius.height;
+ }
+ }
+
+ return P;
+ }
+
+ // This side is larger than other side, this side draws the corner.
+ //
+ // borderWidth / 2.0
+ // |<-->|
+ // | |
+ // +----+---------------------
+ // | ##|## #####
+ // | ###|### #######
+ // |####|#### #########
+ // |####+#### ####+####
+ // |### P ### #########
+ // | ####### #######
+ // | ##### #####
+ // +-----+---------------------
+ // | *** |
+ // |*****|
+ // |**+**| <-- first dot in other side is not filled
+ // |*****|
+ // | *** |
+ // | ### |
+ // |#####|
+ // |##+##|
+ // |#####|
+ // | ### |
+ // | |
+ if (isHorizontal) {
+ P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
+ }
+ return P;
+ }
+
+ if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
+ // If only this side is dotted, other side draws the corner.
+ //
+ // otherBorderWidth + borderWidth / 2.0
+ // |<------->|
+ // | |
+ // +------+--+--------
+ // |## ##| *|* ###
+ // |## ##|**|**#####
+ // |## ##|**+**##+##
+ // |## ##|* P *#####
+ // |## ##| *** ###
+ // |## ##+-----------
+ // |## ##| ^
+ // |## ##| |
+ // |## ##| first dot is not filled
+ // |## ##|
+ //
+ // radius.width
+ // |<----------------->|
+ // | |
+ // | ___---+-------------
+ // | __-- #|# ###
+ // | _- ##|## #####
+ // | / ##+## ##+##
+ // | / # P # #####
+ // | | #|# ###
+ // | | __--+-------------
+ // || _- ^
+ // || / |
+ // | / first dot is filled
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +------+
+ // |## ##|
+ // |## ##|
+ // |## ##|
+ Float minimum = otherBorderWidth + borderWidth / 2.0f;
+ if (isHorizontal) {
+ if (radius.width < minimum) {
+ *aIsUnfilled = true;
+ P.x += signs[0] * minimum;
+ } else {
+ P.x += signs[0] * radius.width;
+ }
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ if (radius.height < minimum) {
+ *aIsUnfilled = true;
+ P.y += signs[1] * minimum;
+ } else {
+ P.y += signs[1] * radius.height;
+ }
+ }
+ return P;
+ }
+
+ if (otherStyle == NS_STYLE_BORDER_STYLE_DOTTED && IsZeroSize(radius)) {
+ // If other side is dotted and radius=0, draw side to the end of corner.
+ //
+ // +-------------------------------
+ // |########## ##########
+ // P +########## ##########
+ // |########## ##########
+ // +-----+-------------------------
+ // | *** |
+ // |*****|
+ // |**+**| <-- first dot in other side is not filled
+ // |*****|
+ // | *** |
+ // | ### |
+ // |#####|
+ // |##+##|
+ // |#####|
+ // | ### |
+ // | |
+ if (isHorizontal) {
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ }
+ return P;
+ }
+
+ // Other cases.
+ //
+ // dim.width
+ // |<----------------->|
+ // | |
+ // | ___---+------------------
+ // | __-- |####### ###
+ // | _- P +####### ###
+ // | / |####### ###
+ // | / __---+------------------
+ // | | __--
+ // | | /
+ // || /
+ // || |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +-+-+
+ // |###|
+ // |###|
+ // |###|
+ // |###|
+ // |###|
+ // | |
+ // | |
+ if (isHorizontal) {
+ P.x += signs[0] * dim.width;
+ P.y += signs[1] * borderWidth / 2.0f;
+ } else {
+ P.x += signs[0] * borderWidth / 2.0f;
+ P.y += signs[1] * dim.height;
+ }
+
+ return P;
+}
+
+void
+nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
+ Bezier* aInnerBezier,
+ mozilla::css::Corner aCorner)
+{
+ // Return bezier control points for outer and inner curve for given corner.
+ //
+ // ___---+ outer curve
+ // __-- |
+ // _- |
+ // / |
+ // / |
+ // | |
+ // | __--+ inner curve
+ // | _-
+ // | /
+ // | /
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +---------+
+
+ mozilla::css::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::css::Side sideV(GetVerticalSide(aCorner));
+
+ Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
+ ceil(mBorderRadii[aCorner].height));
+ Size innerCornerSize(ceil(std::max(0.0f, mBorderRadii[aCorner].width -
+ mBorderWidths[sideV])),
+ ceil(std::max(0.0f, mBorderRadii[aCorner].height -
+ mBorderWidths[sideH])));
+
+ GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
+ outerCornerSize);
+
+ GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
+ innerCornerSize);
+}
+
+void
+nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
+ const Rect& aInnerRect,
+ const RectCornerRadii& aBorderRadii,
+ const Float* aBorderSizes,
+ int aSides,
+ const ColorPattern& aColor)
+{
+ // Note that this function is allowed to draw more than just the
+ // requested sides.
+
+ // If we have a border radius, do full rounded rectangles
+ // and fill, regardless of what sides we're asked to draw.
+ if (!AllCornersZeroSize(aBorderRadii)) {
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+
+ RectCornerRadii innerRadii;
+ ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
+
+ // do the outer border
+ AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
+
+ // then do the inner border CCW
+ AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
+
+ RefPtr<Path> path = builder->Finish();
+
+ mDrawTarget->Fill(path, aColor);
+ return;
+ }
+
+ // If we're asked to draw all sides of an equal-sized border,
+ // stroking is fastest. This is a fairly common path, but partial
+ // sides is probably second in the list -- there are a bunch of
+ // common border styles, such as inset and outset, that are
+ // top-left/bottom-right split.
+ if (aSides == SIDE_BITS_ALL &&
+ CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) &&
+ !mAvoidStroke)
+ {
+ Float strokeWidth = aBorderSizes[0];
+ Rect r(aOuterRect);
+ r.Deflate(strokeWidth / 2.f);
+ mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
+ return;
+ }
+
+ // Otherwise, we have unequal sized borders or we're only
+ // drawing some sides; create rectangles for each side
+ // and fill them.
+
+ Rect r[4];
+
+ // compute base rects for each side
+ if (aSides & SIDE_BIT_TOP) {
+ r[NS_SIDE_TOP] =
+ Rect(aOuterRect.X(), aOuterRect.Y(),
+ aOuterRect.Width(), aBorderSizes[NS_SIDE_TOP]);
+ }
+
+ if (aSides & SIDE_BIT_BOTTOM) {
+ r[NS_SIDE_BOTTOM] =
+ Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[NS_SIDE_BOTTOM],
+ aOuterRect.Width(), aBorderSizes[NS_SIDE_BOTTOM]);
+ }
+
+ if (aSides & SIDE_BIT_LEFT) {
+ r[NS_SIDE_LEFT] =
+ Rect(aOuterRect.X(), aOuterRect.Y(),
+ aBorderSizes[NS_SIDE_LEFT], aOuterRect.Height());
+ }
+
+ if (aSides & SIDE_BIT_RIGHT) {
+ r[NS_SIDE_RIGHT] =
+ Rect(aOuterRect.XMost() - aBorderSizes[NS_SIDE_RIGHT], aOuterRect.Y(),
+ aBorderSizes[NS_SIDE_RIGHT], aOuterRect.Height());
+ }
+
+ // If two sides meet at a corner that we're rendering, then
+ // make sure that we adjust one of the sides to avoid overlap.
+ // This is especially important in the case of colors with
+ // an alpha channel.
+
+ if ((aSides & (SIDE_BIT_TOP | SIDE_BIT_LEFT)) == (SIDE_BIT_TOP | SIDE_BIT_LEFT)) {
+ // adjust the left's top down a bit
+ r[NS_SIDE_LEFT].y += aBorderSizes[NS_SIDE_TOP];
+ r[NS_SIDE_LEFT].height -= aBorderSizes[NS_SIDE_TOP];
+ }
+
+ if ((aSides & (SIDE_BIT_TOP | SIDE_BIT_RIGHT)) == (SIDE_BIT_TOP | SIDE_BIT_RIGHT)) {
+ // adjust the top's left a bit
+ r[NS_SIDE_TOP].width -= aBorderSizes[NS_SIDE_RIGHT];
+ }
+
+ if ((aSides & (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT)) == (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT)) {
+ // adjust the right's bottom a bit
+ r[NS_SIDE_RIGHT].height -= aBorderSizes[NS_SIDE_BOTTOM];
+ }
+
+ if ((aSides & (SIDE_BIT_BOTTOM | SIDE_BIT_LEFT)) == (SIDE_BIT_BOTTOM | SIDE_BIT_LEFT)) {
+ // adjust the bottom's left a bit
+ r[NS_SIDE_BOTTOM].x += aBorderSizes[NS_SIDE_LEFT];
+ r[NS_SIDE_BOTTOM].width -= aBorderSizes[NS_SIDE_LEFT];
+ }
+
+ // Filling these one by one is faster than filling them all at once.
+ for (uint32_t i = 0; i < 4; i++) {
+ if (aSides & (1 << i)) {
+ MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
+ mDrawTarget->FillRect(r[i], aColor);
+ }
+ }
+}
+
+Color
+MakeBorderColor(nscolor aColor, nscolor aBackgroundColor,
+ BorderColorStyle aBorderColorStyle)
+{
+ nscolor colors[2];
+ int k = 0;
+
+ switch (aBorderColorStyle) {
+ case BorderColorStyleNone:
+ return Color(0.f, 0.f, 0.f, 0.f); // transparent black
+
+ case BorderColorStyleLight:
+ k = 1;
+ MOZ_FALLTHROUGH;
+ case BorderColorStyleDark:
+ NS_GetSpecial3DColors(colors, aBackgroundColor, aColor);
+ return Color::FromABGR(colors[k]);
+
+ case BorderColorStyleSolid:
+ default:
+ return Color::FromABGR(aColor);
+ }
+}
+
+Color
+ComputeColorForLine(uint32_t aLineIndex,
+ const BorderColorStyle* aBorderColorStyle,
+ uint32_t aBorderColorStyleCount,
+ nscolor aBorderColor,
+ nscolor aBackgroundColor)
+{
+ NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
+
+ return MakeBorderColor(aBorderColor, aBackgroundColor,
+ aBorderColorStyle[aLineIndex]);
+}
+
+Color
+ComputeCompositeColorForLine(uint32_t aLineIndex,
+ const nsBorderColors* aBorderColors)
+{
+ while (aLineIndex-- && aBorderColors->mNext)
+ aBorderColors = aBorderColors->mNext;
+
+ return Color::FromABGR(aBorderColors->mColor);
+}
+
+void
+nsCSSBorderRenderer::DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *aCompositeColors)
+{
+ RectCornerRadii radii = mBorderRadii;
+
+ // the generic composite colors path; each border is 1px in size
+ Rect soRect = mOuterRect;
+ Float maxBorderWidth = 0;
+ NS_FOR_CSS_SIDES (i) {
+ maxBorderWidth = std::max(maxBorderWidth, Float(mBorderWidths[i]));
+ }
+
+ Float fakeBorderSizes[4];
+
+ Point itl = mInnerRect.TopLeft();
+ Point ibr = mInnerRect.BottomRight();
+
+ for (uint32_t i = 0; i < uint32_t(maxBorderWidth); i++) {
+ ColorPattern color(ToDeviceColor(
+ ComputeCompositeColorForLine(i, aCompositeColors)));
+
+ Rect siRect = soRect;
+ siRect.Deflate(1.0);
+
+ // now cap the rects to the real mInnerRect
+ Point tl = siRect.TopLeft();
+ Point br = siRect.BottomRight();
+
+ tl.x = std::min(tl.x, itl.x);
+ tl.y = std::min(tl.y, itl.y);
+
+ br.x = std::max(br.x, ibr.x);
+ br.y = std::max(br.y, ibr.y);
+
+ siRect = Rect(tl.x, tl.y, br.x - tl.x , br.y - tl.y);
+
+ fakeBorderSizes[NS_SIDE_TOP] = siRect.TopLeft().y - soRect.TopLeft().y;
+ fakeBorderSizes[NS_SIDE_RIGHT] = soRect.TopRight().x - siRect.TopRight().x;
+ fakeBorderSizes[NS_SIDE_BOTTOM] = soRect.BottomRight().y - siRect.BottomRight().y;
+ fakeBorderSizes[NS_SIDE_LEFT] = siRect.BottomLeft().x - soRect.BottomLeft().x;
+
+ FillSolidBorder(soRect, siRect, radii, fakeBorderSizes, aSides, color);
+
+ soRect = siRect;
+
+ ComputeInnerRadii(radii, fakeBorderSizes, &radii);
+ }
+}
+
+void
+nsCSSBorderRenderer::DrawBorderSides(int aSides)
+{
+ if (aSides == 0 || (aSides & ~SIDE_BITS_ALL) != 0) {
+ NS_WARNING("DrawBorderSides: invalid sides!");
+ return;
+ }
+
+ uint8_t borderRenderStyle = NS_STYLE_BORDER_STYLE_NONE;
+ nscolor borderRenderColor;
+ const nsBorderColors *compositeColors = nullptr;
+
+ uint32_t borderColorStyleCount = 0;
+ BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
+ BorderColorStyle *borderColorStyle = nullptr;
+
+ NS_FOR_CSS_SIDES (i) {
+ if ((aSides & (1 << i)) == 0)
+ continue;
+ borderRenderStyle = mBorderStyles[i];
+ borderRenderColor = mBorderColors[i];
+ compositeColors = mCompositeColors[i];
+ break;
+ }
+
+ if (borderRenderStyle == NS_STYLE_BORDER_STYLE_NONE ||
+ borderRenderStyle == NS_STYLE_BORDER_STYLE_HIDDEN)
+ return;
+
+ if (borderRenderStyle == NS_STYLE_BORDER_STYLE_DASHED ||
+ borderRenderStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
+ // Draw each corner separately, with the given side's color.
+ if (aSides & SIDE_BIT_TOP) {
+ DrawDashedOrDottedCorner(NS_SIDE_TOP, C_TL);
+ } else if (aSides & SIDE_BIT_LEFT) {
+ DrawDashedOrDottedCorner(NS_SIDE_LEFT, C_TL);
+ }
+
+ if (aSides & SIDE_BIT_TOP) {
+ DrawDashedOrDottedCorner(NS_SIDE_TOP, C_TR);
+ } else if (aSides & SIDE_BIT_RIGHT) {
+ DrawDashedOrDottedCorner(NS_SIDE_RIGHT, C_TR);
+ }
+
+ if (aSides & SIDE_BIT_BOTTOM) {
+ DrawDashedOrDottedCorner(NS_SIDE_BOTTOM, C_BL);
+ } else if (aSides & SIDE_BIT_LEFT) {
+ DrawDashedOrDottedCorner(NS_SIDE_LEFT, C_BL);
+ }
+
+ if (aSides & SIDE_BIT_BOTTOM) {
+ DrawDashedOrDottedCorner(NS_SIDE_BOTTOM, C_BR);
+ } else if (aSides & SIDE_BIT_RIGHT) {
+ DrawDashedOrDottedCorner(NS_SIDE_RIGHT, C_BR);
+ }
+ return;
+ }
+
+ // -moz-border-colors is a hack; if we have it for a border, then
+ // it's always drawn solid, and each color is given 1px. The last
+ // color is used for the remainder of the border's size. Just
+ // hand off to another function to do all that.
+ if (compositeColors) {
+ Float maxBorderWidth(0);
+ NS_FOR_CSS_SIDES (i) {
+ maxBorderWidth = std::max(maxBorderWidth, mBorderWidths[i]);
+ }
+ if (maxBorderWidth <= MAX_COMPOSITE_BORDER_WIDTH) {
+ DrawBorderSidesCompositeColors(aSides, compositeColors);
+ return;
+ }
+ NS_WARNING("DrawBorderSides: too large border width for composite colors");
+ }
+
+ // We're not doing compositeColors, so we can calculate the
+ // borderColorStyle based on the specified style. The
+ // borderColorStyle array goes from the outer to the inner style.
+ //
+ // If the border width is 1, we need to change the borderRenderStyle
+ // a bit to make sure that we get the right colors -- e.g. 'ridge'
+ // with a 1px border needs to look like solid, not like 'outset'.
+ if (mOneUnitBorder &&
+ (borderRenderStyle == NS_STYLE_BORDER_STYLE_RIDGE ||
+ borderRenderStyle == NS_STYLE_BORDER_STYLE_GROOVE ||
+ borderRenderStyle == NS_STYLE_BORDER_STYLE_DOUBLE))
+ borderRenderStyle = NS_STYLE_BORDER_STYLE_SOLID;
+
+ switch (borderRenderStyle) {
+ case NS_STYLE_BORDER_STYLE_SOLID:
+ borderColorStyleTopLeft[0] = BorderColorStyleSolid;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleSolid;
+
+ borderColorStyleCount = 1;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_GROOVE:
+ borderColorStyleTopLeft[0] = BorderColorStyleDark;
+ borderColorStyleTopLeft[1] = BorderColorStyleLight;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleLight;
+ borderColorStyleBottomRight[1] = BorderColorStyleDark;
+
+ borderColorStyleCount = 2;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_RIDGE:
+ borderColorStyleTopLeft[0] = BorderColorStyleLight;
+ borderColorStyleTopLeft[1] = BorderColorStyleDark;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleDark;
+ borderColorStyleBottomRight[1] = BorderColorStyleLight;
+
+ borderColorStyleCount = 2;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_DOUBLE:
+ borderColorStyleTopLeft[0] = BorderColorStyleSolid;
+ borderColorStyleTopLeft[1] = BorderColorStyleNone;
+ borderColorStyleTopLeft[2] = BorderColorStyleSolid;
+
+ borderColorStyleBottomRight[0] = BorderColorStyleSolid;
+ borderColorStyleBottomRight[1] = BorderColorStyleNone;
+ borderColorStyleBottomRight[2] = BorderColorStyleSolid;
+
+ borderColorStyleCount = 3;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_INSET:
+ borderColorStyleTopLeft[0] = BorderColorStyleDark;
+ borderColorStyleBottomRight[0] = BorderColorStyleLight;
+
+ borderColorStyleCount = 1;
+ break;
+
+ case NS_STYLE_BORDER_STYLE_OUTSET:
+ borderColorStyleTopLeft[0] = BorderColorStyleLight;
+ borderColorStyleBottomRight[0] = BorderColorStyleDark;
+
+ borderColorStyleCount = 1;
+ break;
+
+ default:
+ NS_NOTREACHED("Unhandled border style!!");
+ break;
+ }
+
+ // The only way to get to here is by having a
+ // borderColorStyleCount < 1 or > 3; this should never happen,
+ // since -moz-border-colors doesn't get handled here.
+ NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
+ "Non-border-colors case with borderColorStyleCount < 1 or > 3; what happened?");
+
+ // The caller should never give us anything with a mix
+ // of TL/BR if the border style would require a
+ // TL/BR split.
+ if (aSides & (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT))
+ borderColorStyle = borderColorStyleBottomRight;
+ else
+ borderColorStyle = borderColorStyleTopLeft;
+
+ // Distribute the border across the available space.
+ Float borderWidths[3][4];
+
+ if (borderColorStyleCount == 1) {
+ NS_FOR_CSS_SIDES (i) {
+ borderWidths[0][i] = mBorderWidths[i];
+ }
+ } else if (borderColorStyleCount == 2) {
+ // with 2 color styles, any extra pixel goes to the outside
+ NS_FOR_CSS_SIDES (i) {
+ borderWidths[0][i] = int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2;
+ borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2;
+ }
+ } else if (borderColorStyleCount == 3) {
+ // with 3 color styles, any extra pixel (or lack of extra pixel)
+ // goes to the middle
+ NS_FOR_CSS_SIDES (i) {
+ if (mBorderWidths[i] == 1.0) {
+ borderWidths[0][i] = 1.f;
+ borderWidths[1][i] = borderWidths[2][i] = 0.f;
+ } else {
+ int32_t rest = int32_t(mBorderWidths[i]) % 3;
+ borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] = (int32_t(mBorderWidths[i]) - rest) / 3;
+
+ if (rest == 1) {
+ borderWidths[1][i] += 1.f;
+ } else if (rest == 2) {
+ borderWidths[0][i] += 1.f;
+ borderWidths[2][i] += 1.f;
+ }
+ }
+ }
+ }
+
+ // make a copy that we can modify
+ RectCornerRadii radii = mBorderRadii;
+
+ Rect soRect(mOuterRect);
+ Rect siRect(mOuterRect);
+
+ // If adjacent side is dotted and radius=0, draw side to the end of corner.
+ //
+ // +--------------------------------
+ // |################################
+ // |
+ // |################################
+ // +-----+--------------------------
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | ### |
+ // |#####|
+ // |#####|
+ // |#####|
+ // | ### |
+ // | |
+ bool noMarginTop = false;
+ bool noMarginRight = false;
+ bool noMarginBottom = false;
+ bool noMarginLeft = false;
+
+ // If there is at least one dotted side, every side is rendered separately.
+ if (IsSingleSide(aSides)) {
+ if (aSides == SIDE_BIT_TOP) {
+ if (mBorderStyles[NS_SIDE_RIGHT] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_TR])) {
+ noMarginRight = true;
+ }
+ if (mBorderStyles[NS_SIDE_LEFT] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_TL])) {
+ noMarginLeft = true;
+ }
+ } else if (aSides == SIDE_BIT_RIGHT) {
+ if (mBorderStyles[NS_SIDE_TOP] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_TR])) {
+ noMarginTop = true;
+ }
+ if (mBorderStyles[NS_SIDE_BOTTOM] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_BR])) {
+ noMarginBottom = true;
+ }
+ } else if (aSides == SIDE_BIT_BOTTOM) {
+ if (mBorderStyles[NS_SIDE_RIGHT] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_BR])) {
+ noMarginRight = true;
+ }
+ if (mBorderStyles[NS_SIDE_LEFT] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_BL])) {
+ noMarginLeft = true;
+ }
+ } else {
+ if (mBorderStyles[NS_SIDE_TOP] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_TL])) {
+ noMarginTop = true;
+ }
+ if (mBorderStyles[NS_SIDE_BOTTOM] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ IsZeroSize(mBorderRadii[C_BL])) {
+ noMarginBottom = true;
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < borderColorStyleCount; i++) {
+ // walk siRect inwards at the start of the loop to get the
+ // correct inner rect.
+ //
+ // If noMarginTop is false:
+ // --------------------+
+ // /|
+ // / |
+ // L |
+ // ----------------+ |
+ // | |
+ // | |
+ //
+ // If noMarginTop is true:
+ // ----------------+<--+
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0],
+ noMarginRight ? 0 : borderWidths[i][1],
+ noMarginBottom ? 0 : borderWidths[i][2],
+ noMarginLeft ? 0 : borderWidths[i][3]));
+
+ if (borderColorStyle[i] != BorderColorStyleNone) {
+ Color c = ComputeColorForLine(i, borderColorStyle, borderColorStyleCount,
+ borderRenderColor, mBackgroundColor);
+ ColorPattern color(ToDeviceColor(c));
+
+ FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color);
+ }
+
+ ComputeInnerRadii(radii, borderWidths[i], &radii);
+
+ // And now soRect is the same as siRect, for the next line in.
+ soRect = siRect;
+ }
+}
+
+void
+nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions,
+ Float aDash[2],
+ mozilla::css::Side aSide,
+ Float aBorderLength, bool isCorner)
+{
+ uint8_t style = mBorderStyles[aSide];
+ Float borderWidth = mBorderWidths[aSide];
+
+ // Dashed line starts and ends with half segment in most case.
+ //
+ // __--+---+---+---+---+---+---+---+---+--__
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // __--+---+---+---+---+---+---+---+---+--__
+ //
+ // If radius=0 and other side is either dotted or 0-width, it starts or ends
+ // with full segment.
+ //
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // +---++--+---+---+---+---+---+---+--++---+
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | ## | | ## |
+ // |####| |####|
+ // |####| |####|
+ // | ## | | ## |
+ // | | | |
+ bool fullStart = false, fullEnd = false;
+ Float halfDash;
+ if (style == NS_STYLE_BORDER_STYLE_DASHED) {
+ if (IsZeroSize(mBorderRadii[GetCCWCorner(aSide)]) &&
+ (mBorderStyles[PREV_SIDE(aSide)] == NS_STYLE_BORDER_STYLE_DOTTED ||
+ mBorderWidths[PREV_SIDE(aSide)] == 0.0f ||
+ borderWidth <= 1.0f)) {
+ fullStart = true;
+ }
+
+ if (IsZeroSize(mBorderRadii[GetCWCorner(aSide)]) &&
+ (mBorderStyles[NEXT_SIDE(aSide)] == NS_STYLE_BORDER_STYLE_DOTTED ||
+ mBorderWidths[NEXT_SIDE(aSide)] == 0.0f)) {
+ fullEnd = true;
+ }
+
+ halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f;
+ } else {
+ halfDash = borderWidth * DOT_LENGTH / 2.0f;
+ }
+
+ if (style == NS_STYLE_BORDER_STYLE_DASHED && aBorderLength > 0.0f) {
+ // The number of half segments, with maximum dash length.
+ int32_t count = floor(aBorderLength / halfDash);
+ Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f;
+
+ if (fullStart && fullEnd) {
+ // count should be 4n + 2
+ //
+ // 1 + 4 + 4 + 1
+ //
+ // | | | | |
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // |###|###| | |###|###| | |###|###|
+ // +---+---+---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 6.0f * minHalfDash)
+ return;
+
+ if (count % 4 == 0) {
+ count += 2;
+ } else if (count % 4 == 1) {
+ count += 1;
+ } else if (count % 4 == 3) {
+ count += 3;
+ }
+ } else if (fullStart || fullEnd) {
+ // count should be 4n + 1
+ //
+ // 1 + 4 + 4
+ //
+ // | | | |
+ // +---+---+---+---+---+---+---+---+---+
+ // |###|###| | |###|###| | |###|
+ // |###|###| | |###|###| | |###|
+ // |###|###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+---+
+ //
+ // 4 + 4 + 1
+ //
+ // | | | |
+ // +---+---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|###|
+ // |###| | |###|###| | |###|###|
+ // |###| | |###|###| | |###|###|
+ // +---+---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 5.0f * minHalfDash)
+ return;
+
+ if (count % 4 == 0) {
+ count += 1;
+ } else if (count % 4 == 2) {
+ count += 3;
+ } else if (count % 4 == 3) {
+ count += 2;
+ }
+ } else {
+ // count should be 4n
+ //
+ // 4 + 4
+ //
+ // | | |
+ // +---+---+---+---+---+---+---+---+
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // |###| | |###|###| | |###|
+ // +---+---+---+---+---+---+---+---+
+
+ // If border is too short, draw solid line.
+ if (aBorderLength < 4.0f * minHalfDash)
+ return;
+
+ if (count % 4 == 1) {
+ count += 3;
+ } else if (count % 4 == 2) {
+ count += 2;
+ } else if (count % 4 == 3) {
+ count += 1;
+ }
+ }
+ halfDash = aBorderLength / count;
+ }
+
+ Float fullDash = halfDash * 2.0f;
+
+ aDash[0] = fullDash;
+ aDash[1] = fullDash;
+
+ if (style == NS_STYLE_BORDER_STYLE_DASHED && fullDash > 1.0f) {
+ if (!fullStart) {
+ // Draw half segments on both ends.
+ aStrokeOptions->mDashOffset = halfDash;
+ }
+ } else if (style != NS_STYLE_BORDER_STYLE_DOTTED && isCorner) {
+ // If side ends with filled full segment, corner should start with unfilled
+ // full segment. Not needed for dotted corners, as they overlap one dot with
+ // the side's end.
+ //
+ // corner side
+ // ------------>|<---------------------------
+ // |
+ // __+---+---+---+---+---+---+---+---+
+ // _+- | |###|###| | |###|###| |
+ // /##| | |###|###| | |###|###| |
+ // +####| | |###|###| | |###|###| |
+ // /#\####| _+--+---+---+---+---+---+---+---+
+ // |####\##+-
+ // |#####+-
+ // +--###/
+ // | --+
+ aStrokeOptions->mDashOffset = fullDash;
+ }
+
+ aStrokeOptions->mDashPattern = aDash;
+ aStrokeOptions->mDashLength = 2;
+
+ PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]);
+}
+
+static Float
+GetBorderLength(mozilla::css::Side aSide,
+ const Point& aStart, const Point& aEnd)
+{
+ if (aSide == NS_SIDE_TOP) {
+ return aEnd.x - aStart.x;
+ }
+ if (aSide == NS_SIDE_RIGHT) {
+ return aEnd.y - aStart.y;
+ }
+ if (aSide == NS_SIDE_BOTTOM) {
+ return aStart.x - aEnd.x;
+ }
+ return aStart.y - aEnd.y;
+}
+
+void
+nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::css::Side aSide)
+{
+ // Draw dashed/dotted side with following approach.
+ //
+ // dashed side
+ // Draw dashed line along the side, with appropriate dash length and gap
+ // to make the side symmetric as far as possible. Dash length equals to
+ // the gap, and the ratio of the dash length to border-width is the maximum
+ // value in in [1, 3] range.
+ // In most case, line ends with half segment, to joint with corner easily.
+ // If adjacent side is dotted or 0px and border-radius for the corner
+ // between them is 0, the line ends with full segment.
+ // (see comment for GetStraightBorderPoint for more detail)
+ //
+ // dotted side
+ // If border-width <= 2.0, draw 1:1 dashed line.
+ // Otherwise, draw circles along the side, with appropriate gap that makes
+ // the side symmetric as far as possible. The ratio of the gap to
+ // border-width is the maximum value in [0.5, 1] range in most case.
+ // if the side is too short and there's only 2 dots, it can be more smaller.
+ // If there's no space to place 2 dots at the side, draw single dot at the
+ // middle of the side.
+ // In most case, line ends with filled dot, to joint with corner easily,
+ // If adjacent side is dotted with larger border-width, or other style,
+ // the line ends with unfilled dot.
+ // (see comment for GetStraightBorderPoint for more detail)
+
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
+ mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
+ "Style should be dashed or dotted.");
+
+ Float borderWidth = mBorderWidths[aSide];
+ if (borderWidth == 0.0f) {
+ return;
+ }
+
+ if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED &&
+ borderWidth > 2.0f) {
+ DrawDottedSideSlow(aSide);
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ bool ignored;
+ // Get the start and end points of the side, ensuring that any dot origins get
+ // pushed outward to account for stroking.
+ Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
+ Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
+ if (borderWidth < 2.0f) {
+ // Round start to draw dot on each pixel.
+ if (IsHorizontalSide(aSide)) {
+ start.x = round(start.x);
+ } else {
+ start.y = round(start.y);
+ }
+ }
+
+ Float borderLength = GetBorderLength(aSide, start, end);
+ if (borderLength < 0.0f) {
+ return;
+ }
+
+ StrokeOptions strokeOptions(borderWidth);
+ Float dash[2];
+ SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
+
+ // For dotted sides that can merge with their prior dotted sides, advance the
+ // dash offset to measure the distance around the combined path. This prevents
+ // two dots from bunching together at a corner.
+ mozilla::css::Side mergeSide = aSide;
+ while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
+ mergeSide = PREV_SIDE(mergeSide);
+ // If we looped all the way around, measure starting at the top side, since
+ // we need to pick a fixed location to start measuring distance from still.
+ if (mergeSide == aSide) {
+ mergeSide = eSideTop;
+ break;
+ }
+ }
+ while (mergeSide != aSide) {
+ // Measure the length of the merged side starting from a possibly unmergeable
+ // corner up to the merged corner. A merged corner effectively has no border
+ // radius, so we can just use the cheaper AtCorner to find the end point.
+ Float mergeLength =
+ GetBorderLength(mergeSide,
+ GetStraightBorderPoint(mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
+ mOuterRect.AtCorner(GetCWCorner(mergeSide)));
+ // Add in the merged side length. Also offset the dash progress by an extra
+ // dot's width to avoid drawing a dot that would overdraw where the merged side
+ // would have ended in a gap, i.e. O_O_
+ // O
+ strokeOptions.mDashOffset += mergeLength + borderWidth;
+ mergeSide = NEXT_SIDE(mergeSide);
+ }
+
+ DrawOptions drawOptions;
+ if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED) {
+ drawOptions.mAntialiasMode = AntialiasMode::NONE;
+ }
+
+
+ mDrawTarget->StrokeLine(start, end,
+ ColorPattern(ToDeviceColor(borderColor)),
+ strokeOptions,
+ drawOptions);
+}
+
+void
+nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::css::Side aSide)
+{
+ // Draw each circles separately for dotted with borderWidth > 2.0.
+ // Dashed line with CapStyle::ROUND doesn't render perfect circles.
+
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
+ "Style should be dotted.");
+
+ Float borderWidth = mBorderWidths[aSide];
+ if (borderWidth == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ bool isStartUnfilled, isEndUnfilled;
+ Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide),
+ &isStartUnfilled);
+ Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide),
+ &isEndUnfilled);
+ enum {
+ // Corner is not mergeable.
+ NO_MERGE,
+
+ // Corner between different colors.
+ // Two dots are merged into one, and both side draw half dot.
+ MERGE_HALF,
+
+ // Corner between same colors, CCW corner of the side.
+ // Two dots are merged into one, and this side draw entire dot.
+ //
+ // MERGE_ALL MERGE_NONE
+ // | |
+ // v v
+ // +-----------------------+----+
+ // | ## ## ## | ## |
+ // |#### #### #### |####|
+ // |#### #### #### |####|
+ // | ## ## ## | ## |
+ // +----+------------------+ |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | ## | | ## |
+ // |####| |####|
+ MERGE_ALL,
+
+ // Corner between same colors, CW corner of the side.
+ // Two dots are merged into one, and this side doesn't draw dot.
+ MERGE_NONE
+ } mergeStart = NO_MERGE, mergeEnd = NO_MERGE;
+ if (IsCornerMergeable(GetCCWCorner(aSide))) {
+ if (borderColor == mBorderColors[PREV_SIDE(aSide)]) {
+ mergeStart = MERGE_ALL;
+ } else {
+ mergeStart = MERGE_HALF;
+ }
+ }
+
+ if (IsCornerMergeable(GetCWCorner(aSide))) {
+ if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) {
+ mergeEnd = MERGE_NONE;
+ } else {
+ mergeEnd = MERGE_HALF;
+ }
+ }
+
+ Float borderLength = GetBorderLength(aSide, start, end);
+ if (borderLength < 0.0f) {
+ if (isStartUnfilled || isEndUnfilled) {
+ return;
+ }
+ borderLength = 0.0f;
+ start = end = (start + end) / 2.0f;
+ }
+
+ Float dotWidth = borderWidth * DOT_LENGTH;
+ Float radius = borderWidth / 2.0f;
+ if (borderLength < dotWidth) {
+ // If dots on start and end may overlap, draw a dot at the middle of them.
+ //
+ // ___---+-------+---___
+ // __-- | ##### | --__
+ // #|#######|#
+ // ##|#######|##
+ // ###|#######|###
+ // ###+###+###+###
+ // start ## end #
+ // ##|#######|##
+ // #|#######|#
+ // | ##### |
+ // __--+-------+--__
+ // _- -_
+ //
+ // If that circle overflows from outer rect, do not draw it.
+ //
+ // +-------+
+ // | ##### |
+ // #|#######|#
+ // ##|#######|##
+ // ###|#######|###
+ // ###|###+###|###
+ // ###|#######|###
+ // ##|#######|##
+ // #|#######|#
+ // | ##### |
+ // +--+-+--+
+ // | | | |
+ // | | | |
+ if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius,
+ borderWidth, borderWidth))) {
+ return;
+ }
+
+ if (isStartUnfilled || isEndUnfilled) {
+ return;
+ }
+
+ Point P = (start + end) / 2;
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(P.x + radius, P.y));
+ builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ return;
+ }
+
+ if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
+ // MERGE_HALF
+ // Eo
+ // -------+----+
+ // ##### /
+ // ######/
+ // ######/
+ // ####+
+ // ##/ end
+ // /
+ // /
+ // --+
+ // Ei
+ //
+ // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
+ // Eo
+ // ------------+
+ // ##### |
+ // ####### |
+ // #########|
+ // ####+####|
+ // ## end ##|
+ // ####### |
+ // ##### |
+ // ------------+
+ // Ei
+
+ Point I(0.0f, 0.0f), J(0.0f, 0.0f);
+ if (aSide == NS_SIDE_TOP) {
+ I.x = 1.0f;
+ J.y = 1.0f;
+ } else if (aSide == NS_SIDE_RIGHT) {
+ I.y = 1.0f;
+ J.x = -1.0f;
+ } else if (aSide == NS_SIDE_BOTTOM) {
+ I.x = -1.0f;
+ J.y = -1.0f;
+ } else if (aSide == NS_SIDE_LEFT) {
+ I.y = -1.0f;
+ J.x = 1.0f;
+ }
+
+ Point So, Si, Eo, Ei;
+
+ So = (start + (-I + -J) * borderWidth / 2.0f);
+ Si = (mergeStart == MERGE_HALF)
+ ? (start + (I + J) * borderWidth / 2.0f)
+ : (start + (-I + J) * borderWidth / 2.0f);
+ Eo = (end + (I - J) * borderWidth / 2.0f);
+ Ei = (mergeEnd == MERGE_HALF)
+ ? (end + (-I + J) * borderWidth / 2.0f)
+ : (end + (I + J) * borderWidth / 2.0f);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(So);
+ builder->LineTo(Eo);
+ builder->LineTo(Ei);
+ builder->LineTo(Si);
+ builder->Close();
+ RefPtr<Path> path = builder->Finish();
+
+ mDrawTarget->PushClip(path);
+ }
+
+ size_t count = round(borderLength / dotWidth);
+ if (isStartUnfilled == isEndUnfilled) {
+ // Split into 2n segments.
+ if (count % 2) {
+ count++;
+ }
+ } else {
+ // Split into 2n+1 segments.
+ if (count % 2 == 0) {
+ count++;
+ }
+ }
+
+ // A: radius == borderWidth / 2.0
+ // B: borderLength / count == borderWidth * (1 - overlap)
+ //
+ // A B B B B A
+ // |<-->|<------>|<------>|<------>|<------>|<-->|
+ // | | | | | | |
+ // +----+--------+--------+--------+--------+----+
+ // | ##|## **|** ##|## **|** ##|## |
+ // | ###|### ***|*** ###|### ***|*** ###|### |
+ // |####|####****|****####|####****|****####|####|
+ // |####+####****+****####+####****+****####+####|
+ // |# start #****|****####|####****|****## end ##|
+ // | ###|### ***|*** ###|### ***|*** ###|### |
+ // | ##|## **|** ##|## **|** ##|## |
+ // +----+----+---+--------+--------+---+----+----+
+ // | | | |
+ // | | | |
+
+ // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
+ // 2j points.
+ size_t from = isStartUnfilled ? 1 : 0;
+
+ // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
+ size_t to = count;
+ if (mergeEnd == MERGE_NONE) {
+ if (to > 2) {
+ to -= 2;
+ } else {
+ to = 0;
+ }
+ }
+
+ Point fromP = (start * (count - from) + end * from) / count;
+ Point toP = (start * (count - to) + end * to) / count;
+ // Extend dirty rect to avoid clipping pixel for anti-aliasing.
+ const Float AA_MARGIN = 2.0f;
+
+ if (aSide == NS_SIDE_TOP) {
+ // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
+ // to render only paths that may overlap mDirtyRect.
+ //
+ // mDirtyRect + radius margin
+ // +--+---------------------+--+
+ // | |
+ // | mDirtyRect |
+ // + +---------------------+ +
+ // from ===> |from to | <=== to
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ // ### |### ### ###| ###
+ // ##### ##### ##### ##### #####
+ // ##### ##### ##### ##### #####
+ // ##### ##### ##### ##### #####
+ // ### |### ### ###| ###
+ // | | | |
+ // + +---------------------+ +
+ // | |
+ // | |
+ // +--+---------------------+--+
+
+ Float left = mDirtyRect.x - radius - AA_MARGIN;
+ if (fromP.x < left) {
+ size_t tmp = ceil(count * (left - start.x) / (end.x - start.x));
+ if (tmp > from) {
+ // We increment by 2, so odd/even should match between before/after.
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
+ if (toP.x > right) {
+ size_t tmp = floor(count * (right - start.x) / (end.x - start.x));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == NS_SIDE_RIGHT) {
+ Float top = mDirtyRect.y - radius - AA_MARGIN;
+ if (fromP.y < top) {
+ size_t tmp = ceil(count * (top - start.y) / (end.y - start.y));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
+ if (toP.y > bottom) {
+ size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == NS_SIDE_BOTTOM) {
+ Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
+ if (fromP.x > right) {
+ size_t tmp = ceil(count * (right - start.x) / (end.x - start.x));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float left = mDirtyRect.x - radius - AA_MARGIN;
+ if (toP.x < left) {
+ size_t tmp = floor(count * (left - start.x) / (end.x - start.x));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ } else if (aSide == NS_SIDE_LEFT) {
+ Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
+ if (fromP.y > bottom) {
+ size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y));
+ if (tmp > from) {
+ if ((tmp & 1) != (from & 1)) {
+ from = tmp - 1;
+ } else {
+ from = tmp;
+ }
+ }
+ }
+ Float top = mDirtyRect.y - radius - AA_MARGIN;
+ if (toP.y < top) {
+ size_t tmp = floor(count * (top - start.y) / (end.y - start.y));
+ if (tmp < to) {
+ if ((tmp & 1) != (to & 1)) {
+ to = tmp + 1;
+ } else {
+ to = tmp;
+ }
+ }
+ }
+ }
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ for (size_t i = from; i <= to; i += 2) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ Point P = (start * (count - i) + end * i) / count;
+ builder->MoveTo(Point(P.x + radius, P.y));
+ builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
+ segmentCount++;
+ }
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+
+ if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
+ mDrawTarget->PopClip();
+ }
+}
+
+void
+nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner)
+{
+ // Draw dashed/dotted corner with following approach.
+ //
+ // dashed corner
+ // If both side has same border-width and border-width <= 2.0, draw dashed
+ // line along the corner, with appropriate dash length and gap to make the
+ // corner symmetric as far as possible. Dash length equals to the gap, and
+ // the ratio of the dash length to border-width is the maximum value in in
+ // [1, 3] range.
+ // Otherwise, draw dashed segments along the corner, keeping same dash
+ // length ratio to border-width at that point.
+ // (see DashedCornerFinder.h for more detail)
+ // Line ends with half segments, to joint with both side easily.
+ //
+ // dotted corner
+ // If both side has same border-width and border-width <= 2.0, draw 1:1
+ // dashed line along the corner.
+ // Otherwise Draw circles along the corner, with appropriate gap that makes
+ // the corner symmetric as far as possible. The size of the circle may
+ // change along the corner, that is tangent to the outer curver and the
+ // inner curve. The ratio of the gap to circle diameter is the maximum
+ // value in [0.5, 1] range.
+ // (see DottedCornerFinder.h for more detail)
+ // Corner ends with filled dots but those dots are drawn by
+ // DrawDashedOrDottedSide. So this may draw no circles if there's no space
+ // between 2 dots at both ends.
+
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
+ mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
+ "Style should be dashed or dotted.");
+
+ if (IsCornerMergeable(aCorner)) {
+ // DrawDashedOrDottedSide will draw corner.
+ return;
+ }
+
+ mozilla::css::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::css::Side sideV(GetVerticalSide(aCorner));
+ Float borderWidthH = mBorderWidths[sideH];
+ Float borderWidthV = mBorderWidths[sideV];
+ if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
+ return;
+ }
+
+ Float styleH = mBorderStyles[sideH];
+ Float styleV = mBorderStyles[sideV];
+
+ // Corner between dotted and others with radius=0 is drawn by side.
+ if (IsZeroSize(mBorderRadii[aCorner]) &&
+ (styleV == NS_STYLE_BORDER_STYLE_DOTTED ||
+ styleH == NS_STYLE_BORDER_STYLE_DOTTED)) {
+ return;
+ }
+
+ Float maxRadius = std::max(mBorderRadii[aCorner].width,
+ mBorderRadii[aCorner].height);
+ if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) {
+ DrawFallbackSolidCorner(aSide, aCorner);
+ return;
+ }
+
+ if (borderWidthH != borderWidthV || borderWidthH > 2.0f) {
+ uint8_t style = mBorderStyles[aSide];
+ if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
+ DrawDottedCornerSlow(aSide, aCorner);
+ } else {
+ DrawDashedCornerSlow(aSide, aCorner);
+ }
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Point points[4];
+ bool ignored;
+ // Get the start and end points of the corner arc, ensuring that any dot
+ // origins get pushed backwards towards the edges of the corner rect to
+ // account for stroking.
+ points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
+ points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
+ // Round points to draw dot on each pixel.
+ if (borderWidthH < 2.0f) {
+ points[0].x = round(points[0].x);
+ }
+ if (borderWidthV < 2.0f) {
+ points[3].y = round(points[3].y);
+ }
+ points[1] = points[0];
+ points[1].x += kKappaFactor * (points[3].x - points[0].x);
+ points[2] = points[3];
+ points[2].y += kKappaFactor * (points[0].y - points[3].y);
+
+ Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x),
+ fabs(points[0].y - points[3].y));
+
+ Float dash[2];
+ StrokeOptions strokeOptions(borderWidthH);
+ SetupDashedOptions(&strokeOptions, dash, aSide, len, true);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ builder->MoveTo(points[0]);
+ builder->BezierTo(points[1], points[2], points[3]);
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
+ strokeOptions);
+}
+
+void
+nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner)
+{
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
+ "Style should be dotted.");
+
+ mozilla::css::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::css::Side sideV(GetVerticalSide(aCorner));
+ Float R0 = mBorderWidths[sideH] / 2.0f;
+ Float Rn = mBorderWidths[sideV] / 2.0f;
+ if (R0 == 0.0f && Rn == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ bool ignored;
+ Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored);
+ Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored);
+ DottedCornerFinder finder(outerBezier, innerBezier, aCorner,
+ mBorderRadii[aCorner].width,
+ mBorderRadii[aCorner].height,
+ C0, R0, Cn, Rn, mBorderCornerDimensions[aCorner]);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ const Float AA_MARGIN = 2.0f;
+ Rect marginedDirtyRect = mDirtyRect;
+ marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN);
+ bool entered = false;
+ while (finder.HasMore()) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ DottedCornerFinder::Result result = finder.Next();
+
+ if (marginedDirtyRect.Contains(result.C) && result.r > 0) {
+ entered = true;
+ builder->MoveTo(Point(result.C.x + result.r, result.C.y));
+ builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI));
+ segmentCount++;
+ } else if (entered) {
+ break;
+ }
+ }
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+}
+
+static inline bool
+DashedPathOverlapsRect(Rect& pathRect,
+ const Rect& marginedDirtyRect,
+ DashedCornerFinder::Result& result)
+{
+ // Calculate a rect that contains all control points of the |result| path,
+ // and check if it intersects with |marginedDirtyRect|.
+ pathRect.SetRect(result.outerSectionBezier.mPoints[0].x,
+ result.outerSectionBezier.mPoints[0].y, 0, 0);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]);
+ pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]);
+ pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]);
+
+ return pathRect.Intersects(marginedDirtyRect);
+}
+
+void
+nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner)
+{
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED,
+ "Style should be dashed.");
+
+ mozilla::css::Side sideH(GetHorizontalSide(aCorner));
+ mozilla::css::Side sideV(GetVerticalSide(aCorner));
+ Float borderWidthH = mBorderWidths[sideH];
+ Float borderWidthV = mBorderWidths[sideV];
+ if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
+ return;
+ }
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ DashedCornerFinder finder(outerBezier, innerBezier,
+ borderWidthH, borderWidthV,
+ mBorderCornerDimensions[aCorner]);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ size_t segmentCount = 0;
+ const Float AA_MARGIN = 2.0f;
+ Rect marginedDirtyRect = mDirtyRect;
+ marginedDirtyRect.Inflate(AA_MARGIN);
+ Rect pathRect;
+ bool entered = false;
+ while (finder.HasMore()) {
+ if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+ builder = mDrawTarget->CreatePathBuilder();
+ segmentCount = 0;
+ }
+
+ DashedCornerFinder::Result result = finder.Next();
+
+ if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) {
+ entered = true;
+ builder->MoveTo(result.outerSectionBezier.mPoints[0]);
+ builder->BezierTo(result.outerSectionBezier.mPoints[1],
+ result.outerSectionBezier.mPoints[2],
+ result.outerSectionBezier.mPoints[3]);
+ builder->LineTo(result.innerSectionBezier.mPoints[3]);
+ builder->BezierTo(result.innerSectionBezier.mPoints[2],
+ result.innerSectionBezier.mPoints[1],
+ result.innerSectionBezier.mPoints[0]);
+ builder->LineTo(result.outerSectionBezier.mPoints[0]);
+ segmentCount++;
+ } else if (entered) {
+ break;
+ }
+ }
+
+ if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) {
+ // Fill gap before the first section.
+ //
+ // outnerPoint[0]
+ // |
+ // v
+ // _+-----------+--
+ // / \##########|
+ // / \#########|
+ // + \########|
+ // |\ \######|
+ // | \ \#####|
+ // | \ \####|
+ // | \ \##|
+ // | \ \#|
+ // | \ \|
+ // | \ _-+--
+ // +--------------+ ^
+ // | | |
+ // | | innerPoint[0]
+ // | |
+ builder->MoveTo(outerBezier.mPoints[0]);
+ builder->LineTo(innerBezier.mPoints[0]);
+ builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y));
+ builder->LineTo(outerBezier.mPoints[0]);
+ }
+
+ if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) {
+ // Fill gap after the last section.
+ //
+ // outnerPoint[3]
+ // |
+ // |
+ // | _+-----------+--
+ // | / \ |
+ // v/ \ |
+ // + \ |
+ // |\ \ |
+ // |##\ \ |
+ // |####\ \ |
+ // |######\ \ |
+ // |########\ \ |
+ // |##########\ \|
+ // |############\ _-+--
+ // +--------------+<-- innerPoint[3]
+ // | |
+ // | |
+ // | |
+ builder->MoveTo(outerBezier.mPoints[3]);
+ builder->LineTo(innerBezier.mPoints[3]);
+ builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y));
+ builder->LineTo(outerBezier.mPoints[3]);
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+}
+
+void
+nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner)
+{
+ // Render too large dashed or dotted corner with solid style, to avoid hangup
+ // inside DashedCornerFinder and DottedCornerFinder.
+
+ NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
+ mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
+ "Style should be dashed or dotted.");
+
+ nscolor borderColor = mBorderColors[aSide];
+ Bezier outerBezier;
+ Bezier innerBezier;
+ GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
+
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+
+ builder->MoveTo(outerBezier.mPoints[0]);
+ builder->BezierTo(outerBezier.mPoints[1],
+ outerBezier.mPoints[2],
+ outerBezier.mPoints[3]);
+ builder->LineTo(innerBezier.mPoints[3]);
+ builder->BezierTo(innerBezier.mPoints[2],
+ innerBezier.mPoints[1],
+ innerBezier.mPoints[0]);
+ builder->LineTo(outerBezier.mPoints[0]);
+
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+
+ if (mDocument) {
+ if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
+ mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("CSS"),
+ mDocument,
+ nsContentUtils::eCSS_PROPERTIES,
+ mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED
+ ? "TooLargeDashedRadius"
+ : "TooLargeDottedRadius");
+ }
+ }
+}
+
+bool
+nsCSSBorderRenderer::AllBordersSameWidth()
+{
+ if (mBorderWidths[0] == mBorderWidths[1] &&
+ mBorderWidths[0] == mBorderWidths[2] &&
+ mBorderWidths[0] == mBorderWidths[3])
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsCSSBorderRenderer::AllBordersSolid(bool *aHasCompositeColors)
+{
+ *aHasCompositeColors = false;
+ NS_FOR_CSS_SIDES(i) {
+ if (mCompositeColors[i] != nullptr) {
+ *aHasCompositeColors = true;
+ }
+ if (mBorderStyles[i] == NS_STYLE_BORDER_STYLE_SOLID ||
+ mBorderStyles[i] == NS_STYLE_BORDER_STYLE_NONE ||
+ mBorderStyles[i] == NS_STYLE_BORDER_STYLE_HIDDEN)
+ {
+ continue;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool IsVisible(int aStyle)
+{
+ if (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
+ aStyle != NS_STYLE_BORDER_STYLE_HIDDEN) {
+ return true;
+ }
+ return false;
+}
+
+struct twoFloats
+{
+ Float a, b;
+
+ twoFloats operator*(const Size& aSize) const {
+ return { a * aSize.width, b * aSize.height };
+ }
+
+ twoFloats operator*(Float aScale) const {
+ return { a * aScale, b * aScale };
+ }
+
+ twoFloats operator+(const Point& aPoint) const {
+ return { a + aPoint.x, b + aPoint.y };
+ }
+
+ operator Point() const {
+ return Point(a, b);
+ }
+};
+
+void
+nsCSSBorderRenderer::DrawSingleWidthSolidBorder()
+{
+ // Easy enough to deal with.
+ Rect rect = mOuterRect;
+ rect.Deflate(0.5);
+
+ const twoFloats cornerAdjusts[4] = { { +0.5, 0 },
+ { 0, +0.5 },
+ { -0.5, 0 },
+ { 0, -0.5 } };
+ NS_FOR_CSS_SIDES(side) {
+ Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
+ Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
+
+ ColorPattern color(ToDeviceColor(mBorderColors[side]));
+
+ mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
+ }
+}
+
+// Intersect a ray from the inner corner to the outer corner
+// with the border radius, yielding the intersection point.
+static Point
+IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
+ const Point& aInnerCorner,
+ const Point& aCornerDirection)
+{
+ Point toCorner = aCornerDirection;
+ // transform to-corner ray to unit-circle space
+ toCorner.x /= aRadius.width;
+ toCorner.y /= aRadius.height;
+ // normalize to-corner ray
+ Float cornerDist = toCorner.Length();
+ if (cornerDist < 1.0e-6f) {
+ return aInnerCorner;
+ }
+ toCorner = toCorner / cornerDist;
+ // ray from inner corner to border radius center
+ Point toCenter = aCenter - aInnerCorner;
+ // transform to-center ray to unit-circle space
+ toCenter.x /= aRadius.width;
+ toCenter.y /= aRadius.height;
+ // compute offset of intersection with border radius unit circle
+ Float offset = toCenter.DotProduct(toCorner);
+ // compute discriminant to check for intersections
+ Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
+ // choose farthest intersection
+ offset += sqrtf(std::max(discrim, 0.0f));
+ // transform to-corner ray back out of unit-circle space
+ toCorner.x *= aRadius.width;
+ toCorner.y *= aRadius.height;
+ return aInnerCorner + toCorner * offset;
+}
+
+// Calculate the split point and split angle for a border radius with
+// differing sides.
+static inline void
+SplitBorderRadius(const Point& aCenter, const Size& aRadius,
+ const Point& aOuterCorner, const Point& aInnerCorner,
+ const twoFloats& aCornerMults, Float aStartAngle,
+ Point& aSplit, Float& aSplitAngle)
+{
+ Point cornerDir = aOuterCorner - aInnerCorner;
+ if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
+ // optimize 45-degree intersection with circle since we can assume
+ // the circle center lies along the intersection edge
+ aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
+ aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
+ } else {
+ aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
+ aSplitAngle =
+ atan2f((aSplit.y - aCenter.y) / aRadius.height,
+ (aSplit.x - aCenter.x) / aRadius.width);
+ }
+}
+
+// Compute the size of the skirt needed, given the color alphas
+// of each corner side and the slope between them.
+static void
+ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2,
+ Float aSlopeY, Float aSlopeX,
+ Float& aSizeResult, Float& aSlopeResult)
+{
+ // If either side is (almost) invisible or there is no diagonal edge,
+ // then don't try to render a skirt.
+ if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
+ return;
+ }
+ aSlopeX = fabs(aSlopeX);
+ aSlopeY = fabs(aSlopeY);
+ if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
+ return;
+ }
+
+ // If first and second color don't match, we need to split the corner in
+ // half. The diagonal edges created may not have full pixel coverage given
+ // anti-aliasing, so we need to compute a small subpixel skirt edge. This
+ // assumes each half has half coverage to start with, and that coverage
+ // increases as the skirt is pushed over, with the end result that we want
+ // to roughly preserve the alpha value along this edge.
+ // Given slope m, alphas a and A, use quadratic formula to solve for S in:
+ // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
+ // yielding:
+ // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
+ // and substitute k = (1+m)/(2*m):
+ // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
+ Float slope = aSlopeY / aSlopeX;
+ Float slopeScale = (1.0f + slope) / (2.0f * slope);
+ Float discrim =
+ slopeScale*slopeScale +
+ (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
+ if (discrim >= 0) {
+ aSizeResult = slopeScale - sqrtf(discrim);
+ aSlopeResult = slope;
+ }
+}
+
+// Draws a border radius with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+static void
+DrawBorderRadius(DrawTarget* aDrawTarget,
+ mozilla::css::Corner c,
+ const Point& aOuterCorner, const Point& aInnerCorner,
+ const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
+ const Size& aCornerDims,
+ const Size& aOuterRadius, const Size& aInnerRadius,
+ const Color& aFirstColor, const Color& aSecondColor,
+ Float aSkirtSize, Float aSkirtSlope)
+{
+ // Connect edge to outer arc start point
+ Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+ // Connect edge to outer arc end point
+ Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+ // Connect edge to inner arc start point
+ Point innerCornerStart =
+ outerCornerStart +
+ aCornerMultNext * (aCornerDims - aInnerRadius);
+ // Connect edge to inner arc end point
+ Point innerCornerEnd =
+ outerCornerEnd +
+ aCornerMultPrev * (aCornerDims - aInnerRadius);
+
+ // Outer arc start point
+ Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
+ // Outer arc end point
+ Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
+ // Inner arc start point
+ Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
+ // Inner arc end point
+ Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
+
+ // Outer radius center
+ Point outerCenter = aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
+ // Inner radius center
+ Point innerCenter = aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
+
+ RefPtr<PathBuilder> builder;
+ RefPtr<Path> path;
+
+ if (aFirstColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(outerCornerStart);
+ }
+
+ if (aFirstColor != aSecondColor) {
+ // Start and end angles of corner quadrant
+ Float startAngle = (c * M_PI) / 2.0f - M_PI,
+ endAngle = startAngle + M_PI / 2.0f,
+ outerSplitAngle, innerSplitAngle;
+ Point outerSplit, innerSplit;
+
+ // Outer half-way point
+ SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
+ aCornerMultPrev + aCornerMultNext, startAngle,
+ outerSplit, outerSplitAngle);
+ // Inner half-way point
+ if (aInnerRadius.IsEmpty()) {
+ innerSplit = aInnerCorner;
+ innerSplitAngle = endAngle;
+ } else {
+ SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
+ aCornerMultPrev + aCornerMultNext, startAngle,
+ innerSplit, innerSplitAngle);
+ }
+
+ // Draw first half with first color
+ if (aFirstColor.a > 0) {
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+ outerArcStart, outerSplit, startAngle, outerSplitAngle);
+ // Draw skirt as part of first half
+ if (aSkirtSize > 0) {
+ builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
+ builder->LineTo(innerSplit - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+ innerSplit, innerArcStart, innerSplitAngle, startAngle);
+ if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+ builder->LineTo(innerCornerStart);
+ }
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+
+ // Draw second half with second color
+ if (aSecondColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(outerCornerEnd);
+ if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+ builder->LineTo(innerCornerEnd);
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+ innerArcEnd, innerSplit, endAngle, innerSplitAngle);
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+ outerSplit, outerArcEnd, outerSplitAngle, endAngle);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+ }
+ } else if (aFirstColor.a > 0) {
+ // Draw corner with single color
+ AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
+ outerArcStart, outerArcEnd);
+ builder->LineTo(outerCornerEnd);
+ if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
+ builder->LineTo(innerCornerEnd);
+ }
+ AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
+ innerArcEnd, innerArcStart, -kKappaFactor);
+ if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
+ builder->LineTo(innerCornerStart);
+ }
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+}
+
+// Draw a corner with possibly different sides.
+// A skirt is drawn underneath the corner intersection to hide possible
+// seams when anti-aliased drawing is used.
+static void
+DrawCorner(DrawTarget* aDrawTarget,
+ const Point& aOuterCorner, const Point& aInnerCorner,
+ const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
+ const Size& aCornerDims,
+ const Color& aFirstColor, const Color& aSecondColor,
+ Float aSkirtSize, Float aSkirtSlope)
+{
+ // Corner box start point
+ Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
+ // Corner box end point
+ Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
+
+ RefPtr<PathBuilder> builder;
+ RefPtr<Path> path;
+
+ if (aFirstColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(cornerStart);
+ }
+
+ if (aFirstColor != aSecondColor) {
+ // Draw first half with first color
+ if (aFirstColor.a > 0) {
+ builder->LineTo(aOuterCorner);
+ // Draw skirt as part of first half
+ if (aSkirtSize > 0) {
+ builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
+ builder->LineTo(aInnerCorner - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
+ }
+ builder->LineTo(aInnerCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+
+ // Draw second half with second color
+ if (aSecondColor.a > 0) {
+ builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(cornerEnd);
+ builder->LineTo(aInnerCorner);
+ builder->LineTo(aOuterCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aSecondColor));
+ }
+ } else if (aFirstColor.a > 0) {
+ // Draw corner with single color
+ builder->LineTo(aOuterCorner);
+ builder->LineTo(cornerEnd);
+ builder->LineTo(aInnerCorner);
+ builder->Close();
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(aFirstColor));
+ }
+}
+
+void
+nsCSSBorderRenderer::DrawNoCompositeColorSolidBorder()
+{
+ const twoFloats cornerMults[4] = { { -1, 0 },
+ { 0, -1 },
+ { +1, 0 },
+ { 0, +1 } };
+
+ const twoFloats centerAdjusts[4] = { { 0, +0.5 },
+ { -0.5, 0 },
+ { 0, -0.5 },
+ { +0.5, 0 } };
+
+ RectCornerRadii innerRadii;
+ ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
+
+ Rect strokeRect = mOuterRect;
+ strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0,
+ mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0));
+
+ NS_FOR_CSS_SIDES(i) {
+ // We now draw the current side and the CW corner following it.
+ // The CCW corner of this side was already drawn in the previous iteration.
+ // The side will be drawn as an explicit stroke, and the CW corner will be
+ // filled separately.
+ // If the next side does not have a matching color, then we split the
+ // corner into two halves, one of each side's color and draw both.
+ // Thus, the CCW corner of the next side will end up drawn here.
+
+ // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
+ mozilla::css::Corner c = mozilla::css::Corner((i+1) % 4);
+ mozilla::css::Corner prevCorner = mozilla::css::Corner(i);
+
+ // i+2 and i+3 respectively. These are used to index into the corner
+ // multiplier table, and were deduced by calculating out the long form
+ // of each corner and finding a pattern in the signs and values.
+ int i1 = (i+1) % 4;
+ int i2 = (i+2) % 4;
+ int i3 = (i+3) % 4;
+
+ Float sideWidth = 0.0f;
+ Color firstColor, secondColor;
+ if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) {
+ // draw the side since it is visible
+ sideWidth = mBorderWidths[i];
+ firstColor = ToDeviceColor(mBorderColors[i]);
+ // if the next side is visible, use its color for corner
+ secondColor =
+ IsVisible(mBorderStyles[i1]) && mBorderWidths[i1] ?
+ ToDeviceColor(mBorderColors[i1]) :
+ firstColor;
+ } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) {
+ // assign next side's color to both corner sides
+ firstColor = ToDeviceColor(mBorderColors[i1]);
+ secondColor = firstColor;
+ } else {
+ // neither side is visible, so nothing to do
+ continue;
+ }
+
+ Point outerCorner = mOuterRect.AtCorner(c);
+ Point innerCorner = mInnerRect.AtCorner(c);
+
+ // start and end points of border side stroke between corners
+ Point sideStart =
+ mOuterRect.AtCorner(prevCorner) +
+ cornerMults[i2] * mBorderCornerDimensions[prevCorner];
+ Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
+ // check if the side is visible and not inverted
+ if (sideWidth > 0 && firstColor.a > 0 &&
+ -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) {
+ mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
+ sideEnd + centerAdjusts[i] * sideWidth,
+ ColorPattern(firstColor),
+ StrokeOptions(sideWidth));
+ }
+
+ Float skirtSize = 0.0f, skirtSlope = 0.0f;
+ // the sides don't match, so compute a skirt
+ if (firstColor != secondColor &&
+ mPresContext->Type() != nsPresContext::eContext_Print) {
+ Point cornerDir = outerCorner - innerCorner;
+ ComputeCornerSkirtSize(firstColor.a, secondColor.a,
+ cornerDir.DotProduct(cornerMults[i]),
+ cornerDir.DotProduct(cornerMults[i3]),
+ skirtSize, skirtSlope);
+ }
+
+ if (!mBorderRadii[c].IsEmpty()) {
+ // the corner has a border radius
+ DrawBorderRadius(mDrawTarget,
+ c, outerCorner, innerCorner,
+ cornerMults[i], cornerMults[i3],
+ mBorderCornerDimensions[c],
+ mBorderRadii[c], innerRadii[c],
+ firstColor, secondColor, skirtSize, skirtSlope);
+ } else if (!mBorderCornerDimensions[c].IsEmpty()) {
+ // a corner with no border radius
+ DrawCorner(mDrawTarget,
+ outerCorner, innerCorner,
+ cornerMults[i], cornerMults[i3],
+ mBorderCornerDimensions[c],
+ firstColor, secondColor, skirtSize, skirtSlope);
+ }
+ }
+}
+
+void
+nsCSSBorderRenderer::DrawRectangularCompositeColors()
+{
+ nsBorderColors *currentColors[4];
+ memcpy(currentColors, mCompositeColors, sizeof(nsBorderColors*) * 4);
+ Rect rect = mOuterRect;
+ rect.Deflate(0.5);
+
+ const twoFloats cornerAdjusts[4] = { { +0.5, 0 },
+ { 0, +0.5 },
+ { -0.5, 0 },
+ { 0, -0.5 } };
+
+ for (int i = 0; i < mBorderWidths[0]; i++) {
+ NS_FOR_CSS_SIDES(side) {
+ int sideNext = (side + 1) % 4;
+
+ Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
+ Point secondCorner = rect.CWCorner(side) - cornerAdjusts[side];
+
+ Color currentColor = Color::FromABGR(
+ currentColors[side] ? currentColors[side]->mColor
+ : mBorderColors[side]);
+
+ mDrawTarget->StrokeLine(firstCorner, secondCorner,
+ ColorPattern(ToDeviceColor(currentColor)));
+
+ Point cornerTopLeft = rect.CWCorner(side) - Point(0.5, 0.5);
+ Color nextColor = Color::FromABGR(
+ currentColors[sideNext] ? currentColors[sideNext]->mColor
+ : mBorderColors[sideNext]);
+
+ Color cornerColor((currentColor.r + nextColor.r) / 2.f,
+ (currentColor.g + nextColor.g) / 2.f,
+ (currentColor.b + nextColor.b) / 2.f,
+ (currentColor.a + nextColor.a) / 2.f);
+ mDrawTarget->FillRect(Rect(cornerTopLeft, Size(1, 1)),
+ ColorPattern(ToDeviceColor(cornerColor)));
+
+ if (side != 0) {
+ // We'll have to keep side 0 for the color averaging on side 3.
+ if (currentColors[side] && currentColors[side]->mNext) {
+ currentColors[side] = currentColors[side]->mNext;
+ }
+ }
+ }
+ // Now advance the color for side 0.
+ if (currentColors[0] && currentColors[0]->mNext) {
+ currentColors[0] = currentColors[0]->mNext;
+ }
+ rect.Deflate(1);
+ }
+}
+
+void
+nsCSSBorderRenderer::DrawBorders()
+{
+ bool forceSeparateCorners = false;
+
+ // Examine the border style to figure out if we can draw it in one
+ // go or not.
+ bool tlBordersSame = AreBorderSideFinalStylesSame(SIDE_BIT_TOP | SIDE_BIT_LEFT);
+ bool brBordersSame = AreBorderSideFinalStylesSame(SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT);
+ bool allBordersSame = AreBorderSideFinalStylesSame(SIDE_BITS_ALL);
+ if (allBordersSame &&
+ ((mCompositeColors[0] == nullptr &&
+ (mBorderStyles[0] == NS_STYLE_BORDER_STYLE_NONE ||
+ mBorderStyles[0] == NS_STYLE_BORDER_STYLE_HIDDEN ||
+ mBorderColors[0] == NS_RGBA(0,0,0,0))) ||
+ (mCompositeColors[0] &&
+ (mCompositeColors[0]->mColor == NS_RGBA(0,0,0,0) &&
+ !mCompositeColors[0]->mNext))))
+ {
+ // All borders are the same style, and the style is either none or hidden, or the color
+ // is transparent.
+ // This also checks if the first composite color is transparent, and there are
+ // no others. It doesn't check if there are subsequent transparent ones, because
+ // that would be very silly.
+ return;
+ }
+
+ AutoRestoreTransform autoRestoreTransform;
+ Matrix mat = mDrawTarget->GetTransform();
+
+ // Clamp the CTM to be pixel-aligned; we do this only
+ // for translation-only matrices now, but we could do it
+ // if the matrix has just a scale as well. We should not
+ // do it if there's a rotation.
+ if (mat.HasNonTranslation()) {
+ if (!mat.HasNonAxisAlignedTransform()) {
+ // Scale + transform. Avoid stroke fast-paths so that we have a chance
+ // of snapping to pixel boundaries.
+ mAvoidStroke = true;
+ }
+ } else {
+ mat._31 = floor(mat._31 + 0.5);
+ mat._32 = floor(mat._32 + 0.5);
+ autoRestoreTransform.Init(mDrawTarget);
+ mDrawTarget->SetTransform(mat);
+
+ // round mOuterRect and mInnerRect; they're already an integer
+ // number of pixels apart and should stay that way after
+ // rounding. We don't do this if there's a scale in the current transform
+ // since this loses information that might be relevant when we're scaling.
+ mOuterRect.Round();
+ mInnerRect.Round();
+ }
+
+ bool allBordersSameWidth = AllBordersSameWidth();
+
+ if (allBordersSameWidth && mBorderWidths[0] == 0.0) {
+ // Some of the allBordersSameWidth codepaths depend on the border
+ // width being greater than zero.
+ return;
+ }
+
+ // Initial values only used when the border colors/widths are all the same:
+ ColorPattern color(ToDeviceColor(mBorderColors[NS_SIDE_TOP]));
+ StrokeOptions strokeOptions(mBorderWidths[NS_SIDE_TOP]); // stroke width
+
+ bool allBordersSolid;
+
+ // First there's a couple of 'special cases' that have specifically optimized
+ // drawing paths, when none of these can be used we move on to the generalized
+ // border drawing code.
+ if (allBordersSame &&
+ mCompositeColors[0] == nullptr &&
+ allBordersSameWidth &&
+ mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
+ mNoBorderRadius &&
+ !mAvoidStroke)
+ {
+ // Very simple case.
+ Rect rect = mOuterRect;
+ rect.Deflate(mBorderWidths[0] / 2.0);
+ mDrawTarget->StrokeRect(rect, color, strokeOptions);
+ return;
+ }
+
+ if (allBordersSame &&
+ mCompositeColors[0] == nullptr &&
+ mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
+ !mAvoidStroke &&
+ !mNoBorderRadius)
+ {
+ // Relatively simple case.
+ gfxRect outerRect = ThebesRect(mOuterRect);
+ RoundedRect borderInnerRect(outerRect, mBorderRadii);
+ borderInnerRect.Deflate(mBorderWidths[NS_SIDE_TOP],
+ mBorderWidths[NS_SIDE_BOTTOM],
+ mBorderWidths[NS_SIDE_LEFT],
+ mBorderWidths[NS_SIDE_RIGHT]);
+
+ // Instead of stroking we just use two paths: an inner and an outer.
+ // This allows us to draw borders that we couldn't when stroking. For example,
+ // borders with a border width >= the border radius. (i.e. when there are
+ // square corners on the inside)
+ //
+ // Further, this approach can be more efficient because the backend
+ // doesn't need to compute an offset curve to stroke the path. We know that
+ // the rounded parts are elipses we can offset exactly and can just compute
+ // a new cubic approximation.
+ RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
+ AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
+ AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
+ RefPtr<Path> path = builder->Finish();
+ mDrawTarget->Fill(path, color);
+ return;
+ }
+
+ bool hasCompositeColors;
+
+ allBordersSolid = AllBordersSolid(&hasCompositeColors);
+ // This leaves the border corners non-interpolated for single width borders.
+ // Doing this is slightly faster and shouldn't be a problem visually.
+ if (allBordersSolid &&
+ allBordersSameWidth &&
+ mCompositeColors[0] == nullptr &&
+ mBorderWidths[0] == 1 &&
+ mNoBorderRadius &&
+ !mAvoidStroke)
+ {
+ DrawSingleWidthSolidBorder();
+ return;
+ }
+
+ if (allBordersSolid && !hasCompositeColors &&
+ !mAvoidStroke)
+ {
+ DrawNoCompositeColorSolidBorder();
+ return;
+ }
+
+ if (allBordersSolid &&
+ allBordersSameWidth &&
+ mNoBorderRadius &&
+ !mAvoidStroke)
+ {
+ // Easy enough to deal with.
+ DrawRectangularCompositeColors();
+ return;
+ }
+
+ // If we have composite colors -and- border radius,
+ // then use separate corners so we get OP_ADD for the corners.
+ // Otherwise, we'll get artifacts as we draw stacked 1px-wide curves.
+ if (allBordersSame && mCompositeColors[0] != nullptr && !mNoBorderRadius)
+ forceSeparateCorners = true;
+
+ PrintAsString(" mOuterRect: "), PrintAsString(mOuterRect), PrintAsStringNewline();
+ PrintAsString(" mInnerRect: "), PrintAsString(mInnerRect), PrintAsStringNewline();
+ PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n", mBorderColors[0], mBorderColors[1], mBorderColors[2], mBorderColors[3]);
+
+ // if conditioning the outside rect failed, then bail -- the outside
+ // rect is supposed to enclose the entire border
+ {
+ gfxRect outerRect = ThebesRect(mOuterRect);
+ outerRect.Condition();
+ if (outerRect.IsEmpty())
+ return;
+ mOuterRect = ToRect(outerRect);
+
+ gfxRect innerRect = ThebesRect(mInnerRect);
+ innerRect.Condition();
+ mInnerRect = ToRect(innerRect);
+ }
+
+ int dashedSides = 0;
+
+ NS_FOR_CSS_SIDES(i) {
+ uint8_t style = mBorderStyles[i];
+ if (style == NS_STYLE_BORDER_STYLE_DASHED ||
+ style == NS_STYLE_BORDER_STYLE_DOTTED)
+ {
+ // pretend that all borders aren't the same; we need to draw
+ // things separately for dashed/dotting
+ allBordersSame = false;
+ dashedSides |= (1 << i);
+ }
+ }
+
+ PrintAsFormatString(" allBordersSame: %d dashedSides: 0x%02x\n", allBordersSame, dashedSides);
+
+ if (allBordersSame && !forceSeparateCorners) {
+ /* Draw everything in one go */
+ DrawBorderSides(SIDE_BITS_ALL);
+ PrintAsStringNewline("---------------- (1)");
+ } else {
+ PROFILER_LABEL("nsCSSBorderRenderer", "DrawBorders::multipass",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ /* We have more than one pass to go. Draw the corners separately from the sides. */
+
+ /*
+ * If we have a 1px-wide border, the corners are going to be
+ * negligible, so don't bother doing anything fancy. Just extend
+ * the top and bottom borders to the right 1px and the left border
+ * to the bottom 1px. We do this by twiddling the corner dimensions,
+ * which causes the right to happen later on. Only do this if we have
+ * a 1.0 unit border all around and no border radius.
+ */
+
+ NS_FOR_CSS_CORNERS(corner) {
+ const mozilla::css::Side sides[2] = { mozilla::css::Side(corner), PREV_SIDE(corner) };
+
+ if (!IsZeroSize(mBorderRadii[corner]))
+ continue;
+
+ if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) {
+ if (corner == NS_CORNER_TOP_LEFT || corner == NS_CORNER_TOP_RIGHT)
+ mBorderCornerDimensions[corner].width = 0.0;
+ else
+ mBorderCornerDimensions[corner].height = 0.0;
+ }
+ }
+
+ // First, the corners
+ NS_FOR_CSS_CORNERS(corner) {
+ // if there's no corner, don't do all this work for it
+ if (IsZeroSize(mBorderCornerDimensions[corner]))
+ continue;
+
+ const int sides[2] = { corner, PREV_SIDE(corner) };
+ int sideBits = (1 << sides[0]) | (1 << sides[1]);
+
+ bool simpleCornerStyle = mCompositeColors[sides[0]] == nullptr &&
+ mCompositeColors[sides[1]] == nullptr &&
+ AreBorderSideFinalStylesSame(sideBits);
+
+ // If we don't have anything complex going on in this corner,
+ // then we can just fill the corner with a solid color, and avoid
+ // the potentially expensive clip.
+ if (simpleCornerStyle &&
+ IsZeroSize(mBorderRadii[corner]) &&
+ IsSolidCornerStyle(mBorderStyles[sides[0]], corner))
+ {
+ Color color = MakeBorderColor(mBorderColors[sides[0]],
+ mBackgroundColor,
+ BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner));
+ mDrawTarget->FillRect(GetCornerRect(corner),
+ ColorPattern(ToDeviceColor(color)));
+ continue;
+ }
+
+ // clip to the corner
+ mDrawTarget->PushClipRect(GetCornerRect(corner));
+
+ if (simpleCornerStyle) {
+ // we don't need a group for this corner, the sides are the same,
+ // but we weren't able to render just a solid block for the corner.
+ DrawBorderSides(sideBits);
+ } else {
+ // Sides are different. We could draw using OP_ADD to
+ // get correct color blending behaviour at the seam. We'd need
+ // to do it in an offscreen surface to ensure that we're
+ // always compositing on transparent black. If the colors
+ // don't have transparency and the current destination surface
+ // has an alpha channel, we could just clear the region and
+ // avoid the temporary, but that situation doesn't happen all
+ // that often in practice (we double buffer to no-alpha
+ // surfaces). We choose just to seam though, as the performance
+ // advantages outway the modest easthetic improvement.
+
+ for (int cornerSide = 0; cornerSide < 2; cornerSide++) {
+ mozilla::css::Side side = mozilla::css::Side(sides[cornerSide]);
+ uint8_t style = mBorderStyles[side];
+
+ PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n", corner, cornerSide, side, style);
+
+ RefPtr<Path> path = GetSideClipSubPath(side);
+ mDrawTarget->PushClip(path);
+
+ DrawBorderSides(1 << side);
+
+ mDrawTarget->PopClip();
+ }
+ }
+
+ mDrawTarget->PopClip();
+
+ PrintAsStringNewline();
+ }
+
+ // in the case of a single-unit border, we already munged the
+ // corners up above; so we can just draw the top left and bottom
+ // right sides separately, if they're the same.
+ //
+ // We need to check for mNoBorderRadius, because when there is
+ // one, FillSolidBorder always draws the full rounded rectangle
+ // and expects there to be a clip in place.
+ int alreadyDrawnSides = 0;
+ if (mOneUnitBorder &&
+ mNoBorderRadius &&
+ (dashedSides & (SIDE_BIT_TOP | SIDE_BIT_LEFT)) == 0)
+ {
+ if (tlBordersSame) {
+ DrawBorderSides(SIDE_BIT_TOP | SIDE_BIT_LEFT);
+ alreadyDrawnSides |= (SIDE_BIT_TOP | SIDE_BIT_LEFT);
+ }
+
+ if (brBordersSame && (dashedSides & (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT)) == 0) {
+ DrawBorderSides(SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT);
+ alreadyDrawnSides |= (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT);
+ }
+ }
+
+ // We're done with the corners, now draw the sides.
+ NS_FOR_CSS_SIDES (side) {
+ // if we drew it above, skip it
+ if (alreadyDrawnSides & (1 << side))
+ continue;
+
+ // If there's no border on this side, skip it
+ if (mBorderWidths[side] == 0.0 ||
+ mBorderStyles[side] == NS_STYLE_BORDER_STYLE_HIDDEN ||
+ mBorderStyles[side] == NS_STYLE_BORDER_STYLE_NONE)
+ continue;
+
+
+ if (dashedSides & (1 << side)) {
+ // Dashed sides will always draw just the part ignoring the
+ // corners for the side, so no need to clip.
+ DrawDashedOrDottedSide(side);
+
+ PrintAsStringNewline("---------------- (d)");
+ continue;
+ }
+
+ // Undashed sides will currently draw the entire side,
+ // including parts that would normally be covered by a corner,
+ // so we need to clip.
+ //
+ // XXX Optimization -- it would be good to make this work like
+ // DrawDashedOrDottedSide, and have a DrawOneSide function that just
+ // draws one side and not the corners, because then we can
+ // avoid the potentially expensive clip.
+ mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side));
+
+ DrawBorderSides(1 << side);
+
+ mDrawTarget->PopClip();
+
+ PrintAsStringNewline("---------------- (*)");
+ }
+ }
+}
diff --git a/layout/base/nsCSSRenderingBorders.h b/layout/base/nsCSSRenderingBorders.h
new file mode 100644
index 000000000..6b3bf0759
--- /dev/null
+++ b/layout/base/nsCSSRenderingBorders.h
@@ -0,0 +1,312 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_CSS_RENDERING_BORDERS_H
+#define NS_CSS_RENDERING_BORDERS_H
+
+#include "gfxRect.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BezierUtils.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+
+struct nsBorderColors;
+
+namespace mozilla {
+namespace gfx {
+class GradientStops;
+} // namespace gfx
+} // namespace mozilla
+
+// define this to enable a bunch of debug dump info
+#undef DEBUG_NEW_BORDERS
+
+/*
+ * Helper class that handles border rendering.
+ *
+ * aDrawTarget -- the DrawTarget to which the border should be rendered
+ * outsideRect -- the rectangle on the outer edge of the border
+ *
+ * For any parameter where an array of side values is passed in,
+ * they are in top, right, bottom, left order.
+ *
+ * borderStyles -- one border style enum per side
+ * borderWidths -- one border width per side
+ * borderRadii -- a RectCornerRadii struct describing the w/h for each rounded corner.
+ * If the corner doesn't have a border radius, 0,0 should be given for it.
+ * borderColors -- one nscolor per side
+ * compositeColors -- a pointer to an array of composite color structs, or
+ * nullptr if none.
+ *
+ * skipSides -- a bit mask specifying which sides, if any, to skip
+ * backgroundColor -- the background color of the element.
+ * Used in calculating colors for 2-tone borders, such as inset and outset
+ * gapRect - a rectangle that should be clipped out to leave a gap in a border,
+ * or nullptr if none.
+ */
+
+typedef enum {
+ BorderColorStyleNone,
+ BorderColorStyleSolid,
+ BorderColorStyleLight,
+ BorderColorStyleDark
+} BorderColorStyle;
+
+class nsIDocument;
+class nsPresContext;
+
+class nsCSSBorderRenderer final
+{
+ typedef mozilla::gfx::Bezier Bezier;
+ typedef mozilla::gfx::ColorPattern ColorPattern;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Path Path;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+
+public:
+
+ nsCSSBorderRenderer(nsPresContext* aPresContext,
+ const nsIDocument* aDocument,
+ DrawTarget* aDrawTarget,
+ const Rect& aDirtyRect,
+ Rect& aOuterRect,
+ const uint8_t* aBorderStyles,
+ const Float* aBorderWidths,
+ RectCornerRadii& aBorderRadii,
+ const nscolor* aBorderColors,
+ nsBorderColors* const* aCompositeColors,
+ nscolor aBackgroundColor);
+
+ // draw the entire border
+ void DrawBorders();
+
+ // utility function used for background painting as well as borders
+ static void ComputeInnerRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aInnerRadiiRet);
+
+ // Given aRadii as the border radii for a rectangle, compute the
+ // appropriate radii for another rectangle *outside* that rectangle
+ // by increasing the radii, except keeping sharp corners sharp.
+ // Used for spread box-shadows
+ static void ComputeOuterRadii(const RectCornerRadii& aRadii,
+ const Float* aBorderSizes,
+ RectCornerRadii* aOuterRadiiRet);
+
+private:
+
+ RectCornerRadii mBorderCornerDimensions;
+
+ // Target document to report warning
+ nsPresContext* mPresContext;
+ const nsIDocument* mDocument;
+
+ // destination DrawTarget and dirty rect
+ DrawTarget* mDrawTarget;
+ const Rect& mDirtyRect;
+
+ // the rectangle of the outside and the inside of the border
+ Rect mOuterRect;
+ Rect mInnerRect;
+
+ // the style and size of the border
+ const uint8_t* mBorderStyles;
+ const Float* mBorderWidths;
+ RectCornerRadii mBorderRadii;
+
+ // colors
+ const nscolor* mBorderColors;
+ nsBorderColors* const* mCompositeColors;
+
+ // the background color
+ nscolor mBackgroundColor;
+
+ // calculated values
+ bool mOneUnitBorder;
+ bool mNoBorderRadius;
+ bool mAvoidStroke;
+
+ // For all the sides in the bitmask, would they be rendered
+ // in an identical color and style?
+ bool AreBorderSideFinalStylesSame(uint8_t aSides);
+
+ // For the given style, is the given corner a solid color?
+ bool IsSolidCornerStyle(uint8_t aStyle, mozilla::css::Corner aCorner);
+
+ // For the given corner, is the given corner mergeable into one dot?
+ bool IsCornerMergeable(mozilla::css::Corner aCorner);
+
+ // For the given solid corner, what color style should be used?
+ BorderColorStyle BorderColorStyleForSolidCorner(uint8_t aStyle, mozilla::css::Corner aCorner);
+
+ //
+ // Path generation functions
+ //
+
+ // Get the Rect for drawing the given corner
+ Rect GetCornerRect(mozilla::css::Corner aCorner);
+ // add the path for drawing the given side without any adjacent corners to the context
+ Rect GetSideClipWithoutCornersRect(mozilla::css::Side aSide);
+
+ // Create a clip path for the wedge that this side of
+ // the border should take up. This is only called
+ // when we're drawing separate border sides, so we know
+ // that ADD compositing is taking place.
+ //
+ // This code needs to make sure that the individual pieces
+ // don't ever (mathematically) overlap; the pixel overlap
+ // is taken care of by the ADD compositing.
+ already_AddRefed<Path> GetSideClipSubPath(mozilla::css::Side aSide);
+
+ // Return start or end point for dashed/dotted side
+ Point GetStraightBorderPoint(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner,
+ bool* aIsUnfilled,
+ Float aDotOffset = 0.0f);
+
+ // Return bezier control points for the outer and the inner curve for given
+ // corner
+ void GetOuterAndInnerBezier(Bezier* aOuterBezier,
+ Bezier* aInnerBezier,
+ mozilla::css::Corner aCorner);
+
+ // Given a set of sides to fill and a color, do so in the fastest way.
+ //
+ // Stroke tends to be faster for smaller borders because it doesn't go
+ // through the tessellator, which has initialization overhead. If
+ // we're rendering all sides, we can use stroke at any thickness; we
+ // also do TL/BR pairs at 1px thickness using stroke.
+ //
+ // If we can't stroke, then if it's a TL/BR pair, we use the specific
+ // TL/BR paths. Otherwise, we do the full path and fill.
+ //
+ // Calling code is expected to only set up a clip as necessary; no
+ // clip is needed if we can render the entire border in 1 or 2 passes.
+ void FillSolidBorder(const Rect& aOuterRect,
+ const Rect& aInnerRect,
+ const RectCornerRadii& aBorderRadii,
+ const Float* aBorderSizes,
+ int aSides,
+ const ColorPattern& aColor);
+
+ //
+ // core rendering
+ //
+
+ // draw the border for the given sides, using the style of the first side
+ // present in the bitmask
+ void DrawBorderSides (int aSides);
+
+ // function used by the above to handle -moz-border-colors
+ void DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *compositeColors);
+
+ // Setup the stroke options for the given dashed/dotted side
+ void SetupDashedOptions(StrokeOptions* aStrokeOptions,
+ Float aDash[2], mozilla::css::Side aSide,
+ Float aBorderLength, bool isCorner);
+
+ // Draw the given dashed/dotte side
+ void DrawDashedOrDottedSide(mozilla::css::Side aSide);
+
+ // Draw the given dotted side, each dot separately
+ void DrawDottedSideSlow(mozilla::css::Side aSide);
+
+ // Draw the given dashed/dotted corner
+ void DrawDashedOrDottedCorner(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner);
+
+ // Draw the given dotted corner, each segment separately
+ void DrawDottedCornerSlow(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner);
+
+ // Draw the given dashed corner, each dot separately
+ void DrawDashedCornerSlow(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner);
+
+ // Draw the given dashed/dotted corner with solid style
+ void DrawFallbackSolidCorner(mozilla::css::Side aSide,
+ mozilla::css::Corner aCorner);
+
+ // Analyze if all border sides have the same width.
+ bool AllBordersSameWidth();
+
+ // Analyze if all borders are 'solid' this also considers hidden or 'none'
+ // borders because they can be considered 'solid' borders of 0 width and
+ // with no color effect.
+ bool AllBordersSolid(bool *aHasCompositeColors);
+
+ // Draw a solid color border that is uniformly the same width.
+ void DrawSingleWidthSolidBorder();
+
+ // Draw any border which is solid on all sides and does not use
+ // CompositeColors.
+ void DrawNoCompositeColorSolidBorder();
+
+ // Draw a solid border that has no border radius (i.e. is rectangular) and
+ // uses CompositeColors.
+ void DrawRectangularCompositeColors();
+};
+
+namespace mozilla {
+#ifdef DEBUG_NEW_BORDERS
+#include <stdarg.h>
+
+static inline void PrintAsString(const mozilla::gfx::Point& p) {
+ fprintf (stderr, "[%f,%f]", p.x, p.y);
+}
+
+static inline void PrintAsString(const mozilla::gfx::Size& s) {
+ fprintf (stderr, "[%f %f]", s.width, s.height);
+}
+
+static inline void PrintAsString(const mozilla::gfx::Rect& r) {
+ fprintf (stderr, "[%f %f %f %f]", r.X(), r.Y(), r.Width(), r.Height());
+}
+
+static inline void PrintAsString(const mozilla::gfx::Float f) {
+ fprintf (stderr, "%f", f);
+}
+
+static inline void PrintAsString(const char *s) {
+ fprintf (stderr, "%s", s);
+}
+
+static inline void PrintAsStringNewline(const char *s = nullptr) {
+ if (s)
+ fprintf (stderr, "%s", s);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+}
+
+static inline void PrintAsFormatString(const char *fmt, ...) {
+ va_list vl;
+ va_start(vl, fmt);
+ vfprintf (stderr, fmt, vl);
+ va_end(vl);
+}
+
+#else
+static inline void PrintAsString(const mozilla::gfx::Point& p) {}
+static inline void PrintAsString(const mozilla::gfx::Size& s) {}
+static inline void PrintAsString(const mozilla::gfx::Rect& r) {}
+static inline void PrintAsString(const mozilla::gfx::Float f) {}
+static inline void PrintAsString(const char *s) {}
+static inline void PrintAsStringNewline(const char *s = nullptr) {}
+static inline void PrintAsFormatString(const char *fmt, ...) {}
+#endif
+
+} // namespace mozilla
+
+#endif /* NS_CSS_RENDERING_BORDERS_H */
diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp
new file mode 100644
index 000000000..2f08d156e
--- /dev/null
+++ b/layout/base/nsCaret.cpp
@@ -0,0 +1,968 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=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/. */
+
+/* the caret is the text cursor used, e.g., when editing */
+
+#include "nsCaret.h"
+
+#include <algorithm>
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsITimer.h"
+#include "nsFrameSelection.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsIDOMNode.h"
+#include "nsISelection.h"
+#include "nsISelectionPrivate.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsBlockFrame.h"
+#include "nsISelectionController.h"
+#include "nsTextFrame.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsTextFragment.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Selection.h"
+#include "nsIBidiKeyboard.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+// The bidi indicator hangs off the caret to one side, to show which
+// direction the typing is in. It needs to be at least 2x2 to avoid looking like
+// an insignificant dot
+static const int32_t kMinBidiIndicatorPixels = 2;
+
+/**
+ * Find the first frame in an in-order traversal of the frame subtree rooted
+ * at aFrame which is either a text frame logically at the end of a line,
+ * or which is aStopAtFrame. Return null if no such frame is found. We don't
+ * descend into the children of non-eLineParticipant frames.
+ */
+static nsIFrame*
+CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
+{
+ if (aFrame == aStopAtFrame ||
+ ((aFrame->GetType() == nsGkAtoms::textFrame &&
+ (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
+ return aFrame;
+ if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
+ return nullptr;
+
+ for (nsIFrame* f : aFrame->PrincipalChildList())
+ {
+ nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
+ if (r)
+ return r;
+ }
+ return nullptr;
+}
+
+static nsLineBox*
+FindContainingLine(nsIFrame* aFrame)
+{
+ while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
+ {
+ nsIFrame* parent = aFrame->GetParent();
+ nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
+ if (blockParent)
+ {
+ bool isValid;
+ nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
+ return isValid ? iter.GetLine().get() : nullptr;
+ }
+ aFrame = parent;
+ }
+ return nullptr;
+}
+
+static void
+AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
+{
+ nsLineBox* line = FindContainingLine(*aFrame);
+ if (!line)
+ return;
+ int32_t count = line->GetChildCount();
+ for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
+ {
+ nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
+ if (r == *aFrame)
+ return;
+ if (r)
+ {
+ *aFrame = r;
+ NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
+ *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
+ return;
+ }
+ }
+}
+
+static bool
+IsBidiUI()
+{
+ return Preferences::GetBool("bidi.browser.ui");
+}
+
+nsCaret::nsCaret()
+: mOverrideOffset(0)
+, mBlinkCount(-1)
+, mHideCount(0)
+, mIsBlinkOn(false)
+, mVisible(false)
+, mReadOnly(false)
+, mShowDuringSelection(false)
+, mIgnoreUserModify(true)
+{
+}
+
+nsCaret::~nsCaret()
+{
+ StopBlinking();
+}
+
+nsresult nsCaret::Init(nsIPresShell *inPresShell)
+{
+ NS_ENSURE_ARG(inPresShell);
+
+ mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref
+ NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
+
+ mShowDuringSelection =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
+ mShowDuringSelection ? 1 : 0) != 0;
+
+ // get the selection from the pres shell, and set ourselves up as a selection
+ // listener
+
+ nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
+ if (!selCon)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISelection> domSelection;
+ nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(domSelection));
+ if (NS_FAILED(rv))
+ return rv;
+ if (!domSelection)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
+ if (privateSelection)
+ privateSelection->AddSelectionListener(this);
+ mDomSelectionWeak = do_GetWeakReference(domSelection);
+
+ return NS_OK;
+}
+
+static bool
+DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
+{
+ nsIContent* content = aFrame->GetContent();
+ const nsTextFragment* frag = content->GetText();
+ if (!frag)
+ return false;
+ if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
+ return false;
+ char16_t ch = frag->CharAt(aOffset);
+ return 0x2e80 <= ch && ch <= 0xd7ff;
+}
+
+nsCaret::Metrics
+nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
+{
+ // Compute nominal sizes in appunits
+ nscoord caretWidth =
+ (aCaretHeight * LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) +
+ nsPresContext::CSSPixelsToAppUnits(
+ LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1));
+
+ if (DrawCJKCaret(aFrame, aOffset)) {
+ caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
+ }
+ nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
+ bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
+
+ // Round them to device pixels. Always round down, except that anything
+ // between 0 and 1 goes up to 1 so we don't let the caret disappear.
+ int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Metrics result;
+ result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
+ result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
+ return result;
+}
+
+void nsCaret::Terminate()
+{
+ // this doesn't erase the caret if it's drawn. Should it? We might not have
+ // a good drawing environment during teardown.
+
+ StopBlinking();
+ mBlinkTimer = nullptr;
+
+ // unregiser ourselves as a selection listener
+ nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
+ nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
+ if (privateSelection)
+ privateSelection->RemoveSelectionListener(this);
+ mDomSelectionWeak = nullptr;
+ mPresShell = nullptr;
+
+ mOverrideContent = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
+
+nsISelection* nsCaret::GetSelection()
+{
+ nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
+ return sel;
+}
+
+void nsCaret::SetSelection(nsISelection *aDOMSel)
+{
+ MOZ_ASSERT(aDOMSel);
+ mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::SetVisible(bool inMakeVisible)
+{
+ mVisible = inMakeVisible;
+ mIgnoreUserModify = mVisible;
+ ResetBlinking();
+ SchedulePaint();
+}
+
+bool nsCaret::IsVisible()
+{
+ if (!mVisible || mHideCount) {
+ return false;
+ }
+
+ if (!mShowDuringSelection) {
+ Selection* selection = GetSelectionInternal();
+ if (!selection) {
+ return false;
+ }
+ bool isCollapsed;
+ if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) {
+ return false;
+ }
+ }
+
+ if (IsMenuPopupHidingCaret()) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsCaret::AddForceHide()
+{
+ MOZ_ASSERT(mHideCount < UINT32_MAX);
+ if (++mHideCount > 1) {
+ return;
+ }
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::RemoveForceHide()
+{
+ if (!mHideCount || --mHideCount) {
+ return;
+ }
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
+{
+ mReadOnly = inMakeReadonly;
+ ResetBlinking();
+ SchedulePaint();
+}
+
+/* static */ nsRect
+nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
+ int32_t aFrameOffset,
+ nscoord* aBidiIndicatorSize)
+{
+ nsPoint framePos(0, 0);
+ nsRect rect;
+ nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
+ if (NS_FAILED(rv)) {
+ if (aBidiIndicatorSize) {
+ *aBidiIndicatorSize = 0;
+ }
+ return rect;
+ }
+
+ nsIFrame* frame = aFrame->GetContentInsertionFrame();
+ if (!frame) {
+ frame = aFrame;
+ }
+ NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW),
+ "We should not be in the middle of reflow");
+ nscoord baseline = frame->GetCaretBaseline();
+ nscoord ascent = 0, descent = 0;
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ NS_ASSERTION(fm, "We should be able to get the font metrics");
+ if (fm) {
+ ascent = fm->MaxAscent();
+ descent = fm->MaxDescent();
+ }
+ nscoord height = ascent + descent;
+ WritingMode wm = aFrame->GetWritingMode();
+ bool vertical = wm.IsVertical();
+ if (vertical) {
+ if (wm.IsLineInverted()) {
+ framePos.x = baseline - descent;
+ } else {
+ framePos.x = baseline - ascent;
+ }
+ } else {
+ framePos.y = baseline - ascent;
+ }
+ Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
+ rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) :
+ nsSize(caretMetrics.mCaretWidth, height));
+
+ // Clamp the inline-position to be within our scroll frame. If we don't, then
+ // it clips us, and we don't appear at all. See bug 335560.
+ nsIFrame *scrollFrame =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
+ if (scrollFrame) {
+ // First, use the scrollFrame to get at the scrollable view that we're in.
+ nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
+ nsIFrame *scrolled = sf->GetScrolledFrame();
+ nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
+
+ // Now see if the caret extends beyond the view's bounds. If it does,
+ // then snap it back, put it as close to the edge as it can.
+ if (vertical) {
+ nscoord overflow = caretInScroll.YMost() -
+ scrolled->GetVisualOverflowRectRelativeToSelf().height;
+ if (overflow > 0) {
+ rect.y -= overflow;
+ }
+ } else {
+ nscoord overflow = caretInScroll.XMost() -
+ scrolled->GetVisualOverflowRectRelativeToSelf().width;
+ if (overflow > 0) {
+ rect.x -= overflow;
+ }
+ }
+ }
+
+ if (aBidiIndicatorSize) {
+ *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
+ }
+ return rect;
+}
+
+nsIFrame*
+nsCaret::GetFrameAndOffset(Selection* aSelection,
+ nsINode* aOverrideNode, int32_t aOverrideOffset,
+ int32_t* aFrameOffset)
+{
+ nsINode* focusNode;
+ int32_t focusOffset;
+
+ if (aOverrideNode) {
+ focusNode = aOverrideNode;
+ focusOffset = aOverrideOffset;
+ } else if (aSelection) {
+ focusNode = aSelection->GetFocusNode();
+ aSelection->GetFocusOffset(&focusOffset);
+ } else {
+ return nullptr;
+ }
+
+ if (!focusNode || !focusNode->IsContent()) {
+ return nullptr;
+ }
+
+ nsIContent* contentNode = focusNode->AsContent();
+ nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
+ nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
+ nsIFrame* frame;
+ nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
+ frameSelection, contentNode, focusOffset,
+ frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset);
+ if (NS_FAILED(rv) || !frame) {
+ return nullptr;
+ }
+
+ return frame;
+}
+
+/* static */ nsIFrame*
+nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect)
+{
+ int32_t frameOffset;
+ Selection* selection = aSelection ? aSelection->AsSelection() : nullptr;
+ nsIFrame* frame = GetFrameAndOffset(selection, nullptr, 0, &frameOffset);
+ if (frame) {
+ *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
+ }
+ return frame;
+}
+
+Selection*
+nsCaret::GetSelectionInternal()
+{
+ nsISelection* domSelection = GetSelection();
+ return domSelection ? domSelection->AsSelection() : nullptr;
+}
+
+void nsCaret::SchedulePaint()
+{
+ Selection* selection = GetSelectionInternal();
+ nsINode* focusNode;
+ if (mOverrideContent) {
+ focusNode = mOverrideContent;
+ } else if (selection) {
+ focusNode = selection->GetFocusNode();
+ } else {
+ return;
+ }
+ if (!focusNode || !focusNode->IsContent()) {
+ return;
+ }
+ nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
+ if (!f) {
+ return;
+ }
+ // This may not be the correct continuation frame, but that's OK since we're
+ // just scheduling a paint of the window (or popup).
+ f->SchedulePaint();
+}
+
+void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
+{
+ mShowDuringSelection = aVisibility;
+ SchedulePaint();
+}
+
+void
+nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset)
+{
+ mOverrideContent = do_QueryInterface(aNode);
+ mOverrideOffset = aOffset;
+
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void
+nsCaret::CheckSelectionLanguageChange()
+{
+ if (!IsBidiUI()) {
+ return;
+ }
+
+ bool isKeyboardRTL = false;
+ nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bidiKeyboard->IsLangRTL(&isKeyboardRTL);
+ }
+ // Call SelectionLanguageChange on every paint. Mostly it will be a noop
+ // but it should be fast anyway. This guarantees we never paint the caret
+ // at the wrong place.
+ Selection* selection = GetSelectionInternal();
+ if (selection) {
+ selection->SelectionLanguageChange(isKeyboardRTL);
+ }
+}
+
+nsIFrame*
+nsCaret::GetPaintGeometry(nsRect* aRect)
+{
+ // Return null if we should not be visible.
+ if (!IsVisible() || !mIsBlinkOn) {
+ return nullptr;
+ }
+
+ // Update selection language direction now so the new direction will be
+ // taken into account when computing the caret position below.
+ CheckSelectionLanguageChange();
+
+ int32_t frameOffset;
+ nsIFrame *frame = GetFrameAndOffset(GetSelectionInternal(),
+ mOverrideContent, mOverrideOffset, &frameOffset);
+ if (!frame) {
+ return nullptr;
+ }
+
+ // now we have a frame, check whether it's appropriate to show the caret here
+ const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
+ if ((!mIgnoreUserModify &&
+ userinterface->mUserModify == StyleUserModify::ReadOnly) ||
+ userinterface->mUserInput == StyleUserInput::None ||
+ userinterface->mUserInput == StyleUserInput::Disabled) {
+ return nullptr;
+ }
+
+ // If the offset falls outside of the frame, then don't paint the caret.
+ int32_t startOffset, endOffset;
+ if (frame->GetType() == nsGkAtoms::textFrame &&
+ (NS_FAILED(frame->GetOffsets(startOffset, endOffset)) ||
+ startOffset > frameOffset ||
+ endOffset < frameOffset)) {
+ return nullptr;
+ }
+
+ nsRect caretRect;
+ nsRect hookRect;
+ ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
+
+ aRect->UnionRect(caretRect, hookRect);
+ return frame;
+}
+
+void nsCaret::PaintCaret(DrawTarget& aDrawTarget,
+ nsIFrame* aForFrame,
+ const nsPoint &aOffset)
+{
+ int32_t contentOffset;
+ nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(),
+ mOverrideContent, mOverrideOffset, &contentOffset);
+ if (!frame) {
+ return;
+ }
+ NS_ASSERTION(frame == aForFrame, "We're referring different frame");
+
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect caretRect;
+ nsRect hookRect;
+ ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
+
+ Rect devPxCaretRect =
+ NSRectToSnappedRect(caretRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
+ Rect devPxHookRect =
+ NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
+ ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset)));
+
+ aDrawTarget.FillRect(devPxCaretRect, color);
+ if (!hookRect.IsEmpty()) {
+ aDrawTarget.FillRect(devPxHookRect, color);
+ }
+}
+
+NS_IMETHODIMP
+nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel,
+ int16_t aReason)
+{
+ if ((aReason & nsISelectionListener::MOUSEUP_REASON) || !IsVisible())//this wont do
+ return NS_OK;
+
+ nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
+
+ // The same caret is shared amongst the document and any text widgets it
+ // may contain. This means that the caret could get notifications from
+ // multiple selections.
+ //
+ // If this notification is for a selection that is not the one the
+ // the caret is currently interested in (mDomSelectionWeak), then there
+ // is nothing to do!
+
+ if (domSel != aDomSel)
+ return NS_OK;
+
+ ResetBlinking();
+ SchedulePaint();
+
+ return NS_OK;
+}
+
+void nsCaret::ResetBlinking()
+{
+ mIsBlinkOn = true;
+
+ if (mReadOnly || !mVisible || mHideCount) {
+ StopBlinking();
+ return;
+ }
+
+ if (mBlinkTimer) {
+ mBlinkTimer->Cancel();
+ } else {
+ nsresult err;
+ mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
+ if (NS_FAILED(err))
+ return;
+ }
+
+ uint32_t blinkRate = static_cast<uint32_t>(
+ LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500));
+ if (blinkRate > 0) {
+ mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1);
+ mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+void nsCaret::StopBlinking()
+{
+ if (mBlinkTimer)
+ {
+ mBlinkTimer->Cancel();
+ }
+}
+
+nsresult
+nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection,
+ nsIContent* aContentNode,
+ int32_t aOffset,
+ CaretAssociationHint aFrameHint,
+ nsBidiLevel aBidiLevel,
+ nsIFrame** aReturnFrame,
+ int32_t* aReturnOffset)
+{
+ if (!aFrameSelection)
+ return NS_ERROR_FAILURE;
+ nsIPresShell* presShell = aFrameSelection->GetShell();
+ if (!presShell)
+ return NS_ERROR_FAILURE;
+
+ if (!aContentNode || !aContentNode->IsInComposedDoc() ||
+ presShell->GetDocument() != aContentNode->GetComposedDoc())
+ return NS_ERROR_FAILURE;
+
+ nsIFrame* theFrame = nullptr;
+ int32_t theFrameOffset = 0;
+
+ theFrame = aFrameSelection->GetFrameForNodeOffset(
+ aContentNode, aOffset, aFrameHint, &theFrameOffset);
+ if (!theFrame)
+ return NS_ERROR_FAILURE;
+
+ // if theFrame is after a text frame that's logically at the end of the line
+ // (e.g. if theFrame is a <br> frame), then put the caret at the end of
+ // that text frame instead. This way, the caret will be positioned as if
+ // trailing whitespace was not trimmed.
+ AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
+
+ // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
+ //
+ // Direction Style from visibility->mDirection
+ // ------------------
+ // NS_STYLE_DIRECTION_LTR : LTR or Default
+ // NS_STYLE_DIRECTION_RTL
+ if (theFrame->PresContext()->BidiEnabled())
+ {
+ // If there has been a reflow, take the caret Bidi level to be the level of the current frame
+ if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
+ aBidiLevel = theFrame->GetEmbeddingLevel();
+ }
+
+ int32_t start;
+ int32_t end;
+ nsIFrame* frameBefore;
+ nsIFrame* frameAfter;
+ nsBidiLevel levelBefore; // Bidi level of the character before the caret
+ nsBidiLevel levelAfter; // Bidi level of the character after the caret
+
+ theFrame->GetOffsets(start, end);
+ if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
+ {
+ nsPrevNextBidiLevels levels = aFrameSelection->
+ GetPrevNextBidiLevels(aContentNode, aOffset, false);
+
+ /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
+ if (levels.mFrameBefore || levels.mFrameAfter)
+ {
+ frameBefore = levels.mFrameBefore;
+ frameAfter = levels.mFrameAfter;
+ levelBefore = levels.mLevelBefore;
+ levelAfter = levels.mLevelAfter;
+
+ if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
+ {
+ aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3
+ aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4
+ if (aBidiLevel == levelBefore // rule c1
+ || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
+ IS_SAME_DIRECTION(aBidiLevel, levelBefore)) // rule c5
+ || (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
+ IS_SAME_DIRECTION(aBidiLevel, levelBefore))) // rule c9
+ {
+ if (theFrame != frameBefore)
+ {
+ if (frameBefore) // if there is a frameBefore, move into it
+ {
+ theFrame = frameBefore;
+ theFrame->GetOffsets(start, end);
+ theFrameOffset = end;
+ }
+ else
+ {
+ // if there is no frameBefore, we must be at the beginning of the line
+ // so we stay with the current frame.
+ // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
+ // real frame for the caret to be in. We have to find the visually first frame on the line.
+ nsBidiLevel baseLevel = frameAfter->GetBaseLevel();
+ if (baseLevel != levelAfter)
+ {
+ nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
+ nsPoint(0, 0), false, true, false,
+ true, false);
+ if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
+ theFrame = pos.mResultFrame;
+ theFrameOffset = pos.mContentOffset;
+ }
+ }
+ }
+ }
+ }
+ else if (aBidiLevel == levelAfter // rule c2
+ || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
+ IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // rule c6
+ || (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
+ IS_SAME_DIRECTION(aBidiLevel, levelAfter))) // rule c10
+ {
+ if (theFrame != frameAfter)
+ {
+ if (frameAfter)
+ {
+ // if there is a frameAfter, move into it
+ theFrame = frameAfter;
+ theFrame->GetOffsets(start, end);
+ theFrameOffset = start;
+ }
+ else
+ {
+ // if there is no frameAfter, we must be at the end of the line
+ // so we stay with the current frame.
+ // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
+ // real frame for the caret to be in. We have to find the visually last frame on the line.
+ nsBidiLevel baseLevel = frameBefore->GetBaseLevel();
+ if (baseLevel != levelBefore)
+ {
+ nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0,
+ nsPoint(0, 0), false, true, false,
+ true, false);
+ if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
+ theFrame = pos.mResultFrame;
+ theFrameOffset = pos.mContentOffset;
+ }
+ }
+ }
+ }
+ }
+ else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
+ && IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity
+ && !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity
+ {
+ if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
+ {
+ theFrame->GetOffsets(start, end);
+ levelAfter = theFrame->GetEmbeddingLevel();
+ if (IS_LEVEL_RTL(aBidiLevel)) // c8: caret to the right of the rightmost character
+ theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end;
+ else // c7: caret to the left of the leftmost character
+ theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start;
+ }
+ }
+ else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
+ && IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity
+ && !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity
+ {
+ if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
+ {
+ theFrame->GetOffsets(start, end);
+ levelBefore = theFrame->GetEmbeddingLevel();
+ if (IS_LEVEL_RTL(aBidiLevel)) // c12: caret to the left of the leftmost character
+ theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start;
+ else // c11: caret to the right of the rightmost character
+ theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ *aReturnFrame = theFrame;
+ *aReturnOffset = theFrameOffset;
+ return NS_OK;
+}
+
+size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t total = aMallocSizeOf(this);
+ if (mPresShell) {
+ // We only want the size of the nsWeakReference object, not the PresShell
+ // (since we don't own the PresShell).
+ total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
+ }
+ if (mDomSelectionWeak) {
+ // We only want size of the nsWeakReference object, not the selection
+ // (again, we don't own the selection).
+ total += mDomSelectionWeak->SizeOfOnlyThis(aMallocSizeOf);
+ }
+ if (mBlinkTimer) {
+ total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return total;
+}
+
+bool nsCaret::IsMenuPopupHidingCaret()
+{
+#ifdef MOZ_XUL
+ // Check if there are open popups.
+ nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
+ nsTArray<nsIFrame*> popups;
+ popMgr->GetVisiblePopups(popups);
+
+ if (popups.Length() == 0)
+ return false; // No popups, so caret can't be hidden by them.
+
+ // Get the selection focus content, that's where the caret would
+ // go if it was drawn.
+ nsCOMPtr<nsIDOMNode> node;
+ nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
+ if (!domSelection)
+ return true; // No selection/caret to draw.
+ domSelection->GetFocusNode(getter_AddRefs(node));
+ if (!node)
+ return true; // No selection/caret to draw.
+ nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
+ if (!caretContent)
+ return true; // No selection/caret to draw.
+
+ // If there's a menu popup open before the popup with
+ // the caret, don't show the caret.
+ for (uint32_t i=0; i<popups.Length(); i++) {
+ nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
+ nsIContent* popupContent = popupFrame->GetContent();
+
+ if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
+ // The caret is in this popup. There were no menu popups before this
+ // popup, so don't hide the caret.
+ return false;
+ }
+
+ if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
+ // This is an open menu popup. It does not contain the caret (else we'd
+ // have returned above). Even if the caret is in a subsequent popup,
+ // or another document/frame, it should be hidden.
+ return true;
+ }
+ }
+#endif
+
+ // There are no open menu popups, no need to hide the caret.
+ return false;
+}
+
+void
+nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
+ nsRect* aCaretRect, nsRect* aHookRect)
+{
+ NS_ASSERTION(aFrame, "Should have a frame here");
+
+ WritingMode wm = aFrame->GetWritingMode();
+ bool isVertical = wm.IsVertical();
+
+ nscoord bidiIndicatorSize;
+ *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
+
+ // on RTL frames the right edge of mCaretRect must be equal to framePos
+ const nsStyleVisibility* vis = aFrame->StyleVisibility();
+ if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
+ if (isVertical) {
+ aCaretRect->y -= aCaretRect->height;
+ } else {
+ aCaretRect->x -= aCaretRect->width;
+ }
+ }
+
+ // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
+ aHookRect->SetEmpty();
+ if (!IsBidiUI()) {
+ return;
+ }
+
+ bool isCaretRTL;
+ nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
+ // keyboard direction, or the user has no right-to-left keyboard
+ // installed, so we never draw the hook.
+ if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
+ // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
+ // The height of the hook rectangle is the same as the width of the caret
+ // rectangle.
+ if (isVertical) {
+ bool isSidewaysLR = wm.IsVerticalLR() && !wm.IsLineInverted();
+ if (isSidewaysLR) {
+ aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
+ aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1 :
+ aCaretRect->height),
+ aCaretRect->height,
+ bidiIndicatorSize);
+ } else {
+ aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
+ aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 :
+ aCaretRect->height),
+ aCaretRect->height,
+ bidiIndicatorSize);
+ }
+ } else {
+ aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 :
+ aCaretRect->width),
+ aCaretRect->y + bidiIndicatorSize,
+ bidiIndicatorSize,
+ aCaretRect->width);
+ }
+ }
+}
+
+/* static */
+void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure)
+{
+ nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
+ if (!theCaret) {
+ return;
+ }
+ theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
+ theCaret->SchedulePaint();
+
+ // mBlinkCount of -1 means blink count is not enabled.
+ if (theCaret->mBlinkCount == -1) {
+ return;
+ }
+
+ // Track the blink count, but only at end of a blink cycle.
+ if (!theCaret->mIsBlinkOn) {
+ // If we exceeded the blink count, stop the timer.
+ if (--theCaret->mBlinkCount <= 0) {
+ theCaret->StopBlinking();
+ }
+ }
+}
+
+void
+nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
+{
+ mIgnoreUserModify = aIgnoreUserModify;
+ SchedulePaint();
+}
diff --git a/layout/base/nsCaret.h b/layout/base/nsCaret.h
new file mode 100644
index 000000000..703b33529
--- /dev/null
+++ b/layout/base/nsCaret.h
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=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/. */
+
+/* the caret is the text cursor used, e.g., when editing */
+
+#ifndef nsCaret_h__
+#define nsCaret_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCoord.h"
+#include "nsISelectionListener.h"
+#include "nsIWeakReferenceUtils.h"
+#include "CaretAssociationHint.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+
+class nsDisplayListBuilder;
+class nsFrameSelection;
+class nsIContent;
+class nsIDOMNode;
+class nsIFrame;
+class nsINode;
+class nsIPresShell;
+class nsITimer;
+
+namespace mozilla {
+namespace dom {
+class Selection;
+} // namespace dom
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+class nsCaret final : public nsISelectionListener
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ public:
+ nsCaret();
+
+ protected:
+ virtual ~nsCaret();
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ typedef mozilla::CaretAssociationHint CaretAssociationHint;
+
+ nsresult Init(nsIPresShell *inPresShell);
+ void Terminate();
+
+ void SetSelection(nsISelection *aDOMSel);
+ nsISelection* GetSelection();
+
+ /**
+ * Sets whether the caret should only be visible in nodes that are not
+ * user-modify: read-only, or whether it should be visible in all nodes.
+ *
+ * @param aIgnoreUserModify true to have the cursor visible in all nodes,
+ * false to have it visible in all nodes except
+ * those with user-modify: read-only
+ */
+ void SetIgnoreUserModify(bool aIgnoreUserModify);
+ /** SetVisible will set the visibility of the caret
+ * @param inMakeVisible true to show the caret, false to hide it
+ */
+ void SetVisible(bool intMakeVisible);
+ /** IsVisible will get the visibility of the caret.
+ * This returns false if the caret is hidden because it was set
+ * to not be visible, or because the selection is not collapsed, or
+ * because an open popup is hiding the caret.
+ * It does not take account of blinking or the caret being hidden
+ * because we're in non-editable/disabled content.
+ */
+ bool IsVisible();
+ /**
+ * AddForceHide() increases mHideCount and hide the caret even if
+ * SetVisible(true) has been or will be called. This is useful when the
+ * caller wants to hide caret temporarily and it needs to cancel later.
+ * Especially, in the latter case, it's too difficult to decide if the
+ * caret should be actually visible or not because caret visible state
+ * is set from a lot of event handlers. So, it's very stateful.
+ */
+ void AddForceHide();
+ /**
+ * RemoveForceHide() decreases mHideCount if it's over 0.
+ * If the value becomes 0, this may show the caret if SetVisible(true)
+ * has been called.
+ */
+ void RemoveForceHide();
+ /** SetCaretReadOnly set the appearance of the caret
+ * @param inMakeReadonly true to show the caret in a 'read only' state,
+ * false to show the caret in normal, editing state
+ */
+ void SetCaretReadOnly(bool inMakeReadonly);
+ /**
+ * @param aVisibility true if the caret should be visible even when the
+ * selection is not collapsed.
+ */
+ void SetVisibilityDuringSelection(bool aVisibility);
+
+ /**
+ * Set the caret's position explicitly to the specified node and offset
+ * instead of tracking its selection.
+ * Passing null for aNode would set the caret to track its selection again.
+ **/
+ void SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset);
+
+ /**
+ * Schedule a repaint for the frame where the caret would appear.
+ * Does not check visibility etc.
+ */
+ void SchedulePaint();
+
+ /**
+ * Returns a frame to paint in, and the bounds of the painted caret
+ * relative to that frame.
+ * The rectangle includes bidi decorations.
+ * Returns null if the caret should not be drawn (including if it's blinked
+ * off).
+ */
+ nsIFrame* GetPaintGeometry(nsRect* aRect);
+ /**
+ * A simple wrapper around GetGeometry. Does not take any caret state into
+ * account other than the current selection.
+ */
+ nsIFrame* GetGeometry(nsRect* aRect)
+ {
+ return GetGeometry(GetSelection(), aRect);
+ }
+
+ /** PaintCaret
+ * Actually paint the caret onto the given rendering context.
+ */
+ void PaintCaret(DrawTarget& aDrawTarget,
+ nsIFrame *aForFrame,
+ const nsPoint &aOffset);
+
+ //nsISelectionListener interface
+ NS_DECL_NSISELECTIONLISTENER
+
+ /**
+ * Gets the position and size of the caret that would be drawn for
+ * the focus node/offset of aSelection (assuming it would be drawn,
+ * i.e., disregarding blink status). The geometry is stored in aRect,
+ * and we return the frame aRect is relative to.
+ * Only looks at the focus node of aSelection, so you can call it even if
+ * aSelection is not collapsed.
+ * This rect does not include any extra decorations for bidi.
+ * @param aRect must be non-null
+ */
+ static nsIFrame* GetGeometry(nsISelection* aSelection,
+ nsRect* aRect);
+ static nsresult GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection,
+ nsIContent* aContentNode,
+ int32_t aOffset,
+ CaretAssociationHint aFrameHint,
+ uint8_t aBidiLevel,
+ nsIFrame** aReturnFrame,
+ int32_t* aReturnOffset);
+ static nsRect GetGeometryForFrame(nsIFrame* aFrame,
+ int32_t aFrameOffset,
+ nscoord* aBidiIndicatorSize);
+
+ // Get the frame and frame offset based on the focus node and focus offset
+ // of aSelection. If aOverrideNode and aOverride are provided, use them
+ // instead.
+ // @param aFrameOffset return the frame offset if non-null.
+ // @return the frame of the focus node.
+ static nsIFrame* GetFrameAndOffset(mozilla::dom::Selection* aSelection,
+ nsINode* aOverrideNode,
+ int32_t aOverrideOffset,
+ int32_t* aFrameOffset);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+protected:
+ static void CaretBlinkCallback(nsITimer *aTimer, void *aClosure);
+
+ void CheckSelectionLanguageChange();
+
+ void ResetBlinking();
+ void StopBlinking();
+
+ mozilla::dom::Selection* GetSelectionInternal();
+
+ struct Metrics {
+ nscoord mBidiIndicatorSize; // width and height of bidi indicator
+ nscoord mCaretWidth; // full caret width including bidi indicator
+ };
+ static Metrics ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
+ nscoord aCaretHeight);
+
+ void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
+ nsRect* aCaretRect, nsRect* aHookRect);
+
+ // Returns true if we should not draw the caret because of XUL menu popups.
+ // The caret should be hidden if:
+ // 1. An open popup contains the caret, but a menu popup exists before the
+ // caret-owning popup in the popup list (i.e. a menu is in front of the
+ // popup with the caret). If the menu itself contains the caret we don't
+ // hide it.
+ // 2. A menu popup is open, but there is no caret present in any popup.
+ // 3. The caret selection is empty.
+ bool IsMenuPopupHidingCaret();
+
+ nsWeakPtr mPresShell;
+ nsWeakPtr mDomSelectionWeak;
+
+ nsCOMPtr<nsITimer> mBlinkTimer;
+
+ /**
+ * The content to draw the caret at. If null, we use mDomSelectionWeak's
+ * focus node instead.
+ */
+ nsCOMPtr<nsINode> mOverrideContent;
+ /**
+ * The character offset to draw the caret at.
+ * Ignored if mOverrideContent is null.
+ */
+ int32_t mOverrideOffset;
+ /**
+ * mBlinkCount is used to control the number of times to blink the caret
+ * before stopping the blink. This is reset each time we reset the
+ * blinking.
+ */
+ int32_t mBlinkCount;
+ /**
+ * mHideCount is not 0, it means that somebody doesn't want the caret
+ * to be visible. See AddForceHide() and RemoveForceHide().
+ */
+ uint32_t mHideCount;
+
+ /**
+ * mIsBlinkOn is true when we're in a blink cycle where the caret is on.
+ */
+ bool mIsBlinkOn;
+ /**
+ * mIsVisible is true when SetVisible was last called with 'true'.
+ */
+ bool mVisible;
+ /**
+ * mReadOnly is true when the caret is set to "read only" mode (i.e.,
+ * it doesn't blink).
+ */
+ bool mReadOnly;
+ /**
+ * mShowDuringSelection is true when the caret should be shown even when
+ * the selection is not collapsed.
+ */
+ bool mShowDuringSelection;
+ /**
+ * mIgnoreUserModify is true when the caret should be shown even when
+ * it's in non-user-modifiable content.
+ */
+ bool mIgnoreUserModify;
+};
+
+#endif //nsCaret_h__
diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h
new file mode 100644
index 000000000..318b84840
--- /dev/null
+++ b/layout/base/nsChangeHint.h
@@ -0,0 +1,540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants for what needs to be recomputed in response to style changes */
+
+#ifndef nsChangeHint_h___
+#define nsChangeHint_h___
+
+#include "mozilla/Types.h"
+#include "nsDebug.h"
+#include "nsTArray.h"
+
+struct nsCSSSelector;
+
+// Defines for various style related constants
+
+enum nsChangeHint {
+ // change was visual only (e.g., COLOR=)
+ // Invalidates all descendant frames (including following
+ // placeholders to out-of-flow frames).
+ nsChangeHint_RepaintFrame = 1 << 0,
+
+ // For reflow, we want flags to give us arbitrary FrameNeedsReflow behavior.
+ // just do a FrameNeedsReflow.
+ nsChangeHint_NeedReflow = 1 << 1,
+
+ // Invalidate intrinsic widths on the frame's ancestors. Must not be set
+ // without setting nsChangeHint_NeedReflow.
+ nsChangeHint_ClearAncestorIntrinsics = 1 << 2,
+
+ // Invalidate intrinsic widths on the frame's descendants. Must not be set
+ // without also setting nsChangeHint_ClearAncestorIntrinsics.
+ nsChangeHint_ClearDescendantIntrinsics = 1 << 3,
+
+ // Force unconditional reflow of all descendants. Must not be set without
+ // setting nsChangeHint_NeedReflow, but is independent of both the
+ // Clear*Intrinsics flags.
+ nsChangeHint_NeedDirtyReflow = 1 << 4,
+
+ // change requires view to be updated, if there is one (e.g., clip:).
+ // Updates all descendants (including following placeholders to out-of-flows).
+ nsChangeHint_SyncFrameView = 1 << 5,
+
+ // The currently shown mouse cursor needs to be updated
+ nsChangeHint_UpdateCursor = 1 << 6,
+
+ /**
+ * Used when the computed value (a URI) of one or more of an element's
+ * filter/mask/clip/etc CSS properties changes, causing the element's frame
+ * to start/stop referencing (or reference different) SVG resource elements.
+ * (_Not_ used to handle changes to referenced resource elements.) Using this
+ * hint results in nsSVGEffects::UpdateEffects being called on the element's
+ * frame.
+ */
+ nsChangeHint_UpdateEffects = 1 << 7,
+
+ /**
+ * Visual change only, but the change can be handled entirely by
+ * updating the layer(s) for the frame.
+ * Updates all descendants (including following placeholders to out-of-flows).
+ */
+ nsChangeHint_UpdateOpacityLayer = 1 << 8,
+ /**
+ * Updates all descendants. Any placeholder descendants' out-of-flows
+ * are also descendants of the transformed frame, so they're updated.
+ */
+ nsChangeHint_UpdateTransformLayer = 1 << 9,
+
+ /**
+ * Change requires frame change (e.g., display:).
+ * Reconstructs all frame descendants, including following placeholders
+ * to out-of-flows.
+ *
+ * Note that this subsumes all the other change hints. (see
+ * RestyleManager::ProcessRestyledFrames for details).
+ */
+ nsChangeHint_ReconstructFrame = 1 << 10,
+
+ /**
+ * The frame's overflow area has changed. Does not update any descendant
+ * frames.
+ */
+ nsChangeHint_UpdateOverflow = 1 << 11,
+
+ /**
+ * The overflow area of the frame and all of its descendants has changed. This
+ * can happen through a text-decoration change.
+ */
+ nsChangeHint_UpdateSubtreeOverflow = 1 << 12,
+
+ /**
+ * The frame's overflow area has changed, through a change in its transform.
+ * In other words, the frame's pre-transform overflow is unchanged, but
+ * its post-transform overflow has changed, and thus its effect on its
+ * parent's overflow has changed. If the pre-transform overflow has
+ * changed, see nsChangeHint_UpdateOverflow.
+ * Does not update any descendant frames.
+ */
+ nsChangeHint_UpdatePostTransformOverflow = 1 << 13,
+
+ /**
+ * This frame's effect on its parent's overflow area has changed.
+ * (But neither its pre-transform nor post-transform overflow have
+ * changed; if those are the case, see
+ * nsChangeHint_UpdatePostTransformOverflow.)
+ */
+ nsChangeHint_UpdateParentOverflow = 1 << 14,
+
+ /**
+ * The children-only transform of an SVG frame changed, requiring the
+ * overflow rects of the frame's immediate children to be updated.
+ */
+ nsChangeHint_ChildrenOnlyTransform = 1 << 15,
+
+ /**
+ * The frame's offsets have changed, while its dimensions might have
+ * changed as well. This hint is used for positioned frames if their
+ * offset changes. If we decide that the dimensions are likely to
+ * change, this will trigger a reflow.
+ *
+ * Note that this should probably be used in combination with
+ * nsChangeHint_UpdateOverflow in order to get the overflow areas of
+ * the ancestors updated as well.
+ */
+ nsChangeHint_RecomputePosition = 1 << 16,
+
+ /**
+ * Behaves like ReconstructFrame, but only if the frame has descendants
+ * that are absolutely or fixed position. Use this hint when a style change
+ * has changed whether the frame is a container for fixed-pos or abs-pos
+ * elements, but reframing is otherwise not needed.
+ *
+ * Note that nsStyleContext::CalcStyleDifference adjusts results
+ * returned by style struct CalcDifference methods to return this hint
+ * only if there was a change to whether the element's overall style
+ * indicates that it establishes a containing block.
+ */
+ nsChangeHint_UpdateContainingBlock = 1 << 17,
+
+ /**
+ * This change hint has *no* change handling behavior. However, it
+ * exists to be a non-inherited hint, because when the border-style
+ * changes, and it's inherited by a child, that might require a reflow
+ * due to the border-width change on the child.
+ */
+ nsChangeHint_BorderStyleNoneChange = 1 << 18,
+
+ /**
+ * SVG textPath needs to be recomputed because the path has changed.
+ * This means that the glyph positions of the text need to be recomputed.
+ */
+ nsChangeHint_UpdateTextPath = 1 << 19,
+
+ /**
+ * This will schedule an invalidating paint. This is useful if something
+ * has changed which will be invalidated by DLBI.
+ */
+ nsChangeHint_SchedulePaint = 1 << 20,
+
+ /**
+ * A hint reflecting that style data changed with no change handling
+ * behavior. We need to return this, rather than nsChangeHint(0),
+ * so that certain optimizations that manipulate the style context tree are
+ * correct.
+ *
+ * nsChangeHint_NeutralChange must be returned by CalcDifference on a given
+ * style struct if the data in the style structs are meaningfully different
+ * and if no other change hints are returned. If any other change hints are
+ * set, then nsChangeHint_NeutralChange need not also be included, but it is
+ * safe to do so. (An example of style structs having non-meaningfully
+ * different data would be cached information that would be re-calculated
+ * to the same values, such as nsStyleBorder::mSubImages.)
+ */
+ nsChangeHint_NeutralChange = 1 << 21,
+
+ /**
+ * This will cause rendering observers to be invalidated.
+ */
+ nsChangeHint_InvalidateRenderingObservers = 1 << 22,
+
+ /**
+ * Indicates that the reflow changes the size or position of the
+ * element, and thus the reflow must start from at least the frame's
+ * parent.
+ */
+ nsChangeHint_ReflowChangesSizeOrPosition = 1 << 23,
+
+ /**
+ * Indicates that the style changes the computed BSize --- e.g. 'height'.
+ */
+ nsChangeHint_UpdateComputedBSize = 1 << 24,
+
+ /**
+ * Indicates that the 'opacity' property changed between 1 and non-1.
+ *
+ * Used as extra data for handling UpdateOpacityLayer hints.
+ *
+ * Note that we do not send this hint if the non-1 value was 0.99 or
+ * greater, since in that case we send a RepaintFrame hint instead.
+ */
+ nsChangeHint_UpdateUsesOpacity = 1 << 25,
+
+ /**
+ * Indicates that the 'background-position' property changed.
+ * Regular frames can invalidate these changes using DLBI, but
+ * for some frame types we need to repaint the whole frame because
+ * the frame does not build individual background image display items
+ * for each background layer.
+ */
+ nsChangeHint_UpdateBackgroundPosition = 1 << 26,
+
+ /**
+ * Indicates that a frame has changed to or from having the CSS
+ * transform property set.
+ */
+ nsChangeHint_AddOrRemoveTransform = 1 << 27,
+
+ // IMPORTANT NOTE: When adding new hints, consider whether you need
+ // to add them to NS_HintsNotHandledForDescendantsIn() below. Please
+ // also add them to RestyleManager::ChangeHintToString and modify
+ // nsChangeHint_AllHints below accordingly.
+
+ /**
+ * Dummy hint value for all hints. It exists for compile time check.
+ */
+ nsChangeHint_AllHints = (1 << 28) - 1,
+};
+
+// Redefine these operators to return nothing. This will catch any use
+// of these operators on hints. We should not be using these operators
+// on nsChangeHints
+inline void operator<(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator>(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator!=(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator==(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator<=(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator>=(nsChangeHint s1, nsChangeHint s2) {}
+
+// Operators on nsChangeHints
+
+// Returns true iff the second hint contains all the hints of the first hint
+inline bool NS_IsHintSubset(nsChangeHint aSubset, nsChangeHint aSuperSet) {
+ return (aSubset & aSuperSet) == aSubset;
+}
+
+// The functions below need an integral type to cast to to avoid
+// infinite recursion.
+typedef decltype(nsChangeHint(0) + nsChangeHint(0)) nsChangeHint_size_t;
+
+inline nsChangeHint constexpr
+operator|(nsChangeHint aLeft, nsChangeHint aRight)
+{
+ return nsChangeHint(nsChangeHint_size_t(aLeft) | nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint constexpr
+operator&(nsChangeHint aLeft, nsChangeHint aRight)
+{
+ return nsChangeHint(nsChangeHint_size_t(aLeft) & nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint& operator|=(nsChangeHint& aLeft, nsChangeHint aRight)
+{
+ return aLeft = aLeft | aRight;
+}
+
+inline nsChangeHint& operator&=(nsChangeHint& aLeft, nsChangeHint aRight)
+{
+ return aLeft = aLeft & aRight;
+}
+
+inline nsChangeHint constexpr
+operator~(nsChangeHint aArg)
+{
+ return nsChangeHint(~nsChangeHint_size_t(aArg));
+}
+
+inline nsChangeHint constexpr
+operator^(nsChangeHint aLeft, nsChangeHint aRight)
+{
+ return nsChangeHint(nsChangeHint_size_t(aLeft) ^ nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint operator^=(nsChangeHint& aLeft, nsChangeHint aRight)
+{
+ return aLeft = aLeft ^ aRight;
+}
+
+/**
+ * We have an optimization when processing change hints which prevents
+ * us from visiting the descendants of a node when a hint on that node
+ * is being processed. This optimization does not apply in some of the
+ * cases where applying a hint to an element does not necessarily result
+ * in the same hint being handled on the descendants.
+ */
+
+// The most hints that NS_HintsNotHandledForDescendantsIn could possibly return:
+#define nsChangeHint_Hints_NotHandledForDescendants nsChangeHint( \
+ nsChangeHint_UpdateTransformLayer | \
+ nsChangeHint_UpdateEffects | \
+ nsChangeHint_InvalidateRenderingObservers | \
+ nsChangeHint_UpdateOpacityLayer | \
+ nsChangeHint_UpdateOverflow | \
+ nsChangeHint_UpdatePostTransformOverflow | \
+ nsChangeHint_UpdateParentOverflow | \
+ nsChangeHint_ChildrenOnlyTransform | \
+ nsChangeHint_RecomputePosition | \
+ nsChangeHint_UpdateContainingBlock | \
+ nsChangeHint_AddOrRemoveTransform | \
+ nsChangeHint_BorderStyleNoneChange | \
+ nsChangeHint_NeedReflow | \
+ nsChangeHint_ReflowChangesSizeOrPosition | \
+ nsChangeHint_ClearAncestorIntrinsics | \
+ nsChangeHint_UpdateComputedBSize | \
+ nsChangeHint_UpdateUsesOpacity | \
+ nsChangeHint_UpdateBackgroundPosition)
+
+inline nsChangeHint NS_HintsNotHandledForDescendantsIn(nsChangeHint aChangeHint) {
+ nsChangeHint result = nsChangeHint(aChangeHint & (
+ nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_UpdateEffects |
+ nsChangeHint_InvalidateRenderingObservers |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateParentOverflow |
+ nsChangeHint_ChildrenOnlyTransform |
+ nsChangeHint_RecomputePosition |
+ nsChangeHint_UpdateContainingBlock |
+ nsChangeHint_AddOrRemoveTransform |
+ nsChangeHint_BorderStyleNoneChange |
+ nsChangeHint_UpdateComputedBSize |
+ nsChangeHint_UpdateUsesOpacity | \
+ nsChangeHint_UpdateBackgroundPosition));
+
+ if (!NS_IsHintSubset(nsChangeHint_NeedDirtyReflow, aChangeHint)) {
+ if (NS_IsHintSubset(nsChangeHint_NeedReflow, aChangeHint)) {
+ // If NeedDirtyReflow is *not* set, then NeedReflow is a
+ // non-inherited hint.
+ result |= nsChangeHint_NeedReflow;
+ }
+
+ if (NS_IsHintSubset(nsChangeHint_ReflowChangesSizeOrPosition,
+ aChangeHint)) {
+ // If NeedDirtyReflow is *not* set, then ReflowChangesSizeOrPosition is a
+ // non-inherited hint.
+ result |= nsChangeHint_ReflowChangesSizeOrPosition;
+ }
+ }
+
+ if (!NS_IsHintSubset(nsChangeHint_ClearDescendantIntrinsics, aChangeHint) &&
+ NS_IsHintSubset(nsChangeHint_ClearAncestorIntrinsics, aChangeHint)) {
+ // If ClearDescendantIntrinsics is *not* set, then
+ // ClearAncestorIntrinsics is a non-inherited hint.
+ result |= nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ MOZ_ASSERT(NS_IsHintSubset(result,
+ nsChangeHint_Hints_NotHandledForDescendants),
+ "something is inconsistent");
+
+ return result;
+}
+
+// Redefine the old NS_STYLE_HINT constants in terms of the new hint structure
+#define NS_STYLE_HINT_VISUAL \
+ nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | \
+ nsChangeHint_SchedulePaint)
+#define nsChangeHint_AllReflowHints \
+ nsChangeHint(nsChangeHint_NeedReflow | \
+ nsChangeHint_ReflowChangesSizeOrPosition|\
+ nsChangeHint_ClearAncestorIntrinsics | \
+ nsChangeHint_ClearDescendantIntrinsics | \
+ nsChangeHint_NeedDirtyReflow)
+#define NS_STYLE_HINT_REFLOW \
+ nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints)
+
+#define nsChangeHint_Hints_CanIgnoreIfNotVisible \
+ nsChangeHint(NS_STYLE_HINT_VISUAL | \
+ nsChangeHint_NeutralChange | \
+ nsChangeHint_UpdateOpacityLayer | \
+ nsChangeHint_UpdateTransformLayer | \
+ nsChangeHint_UpdateUsesOpacity)
+
+/**
+ * |nsRestyleHint| is a bitfield for the result of
+ * |HasStateDependentStyle| and |HasAttributeDependentStyle|. When no
+ * restyling is necessary, use |nsRestyleHint(0)|.
+ *
+ * Without eRestyle_Force or eRestyle_ForceDescendants, the restyling process
+ * can stop processing at a frame when it detects no style changes and it is
+ * known that the styles of the subtree beneath it will not change, leaving
+ * the old style context on the frame. eRestyle_Force can be used to skip this
+ * optimization on a frame, and to force its new style context to be used.
+ *
+ * Similarly, eRestyle_ForceDescendants will cause the frame and all of its
+ * descendants to be traversed and for the new style contexts that are created
+ * to be set on the frames.
+ *
+ * NOTE: When adding new restyle hints, please also add them to
+ * RestyleManager::RestyleHintToString.
+ */
+enum nsRestyleHint {
+ // Rerun selector matching on the element. If a new style context
+ // results, update the style contexts of descendants. (Irrelevant if
+ // eRestyle_Subtree is also set, since that implies a superset of the
+ // work.)
+ eRestyle_Self = 1 << 0,
+
+ // Rerun selector matching on descendants of the element that match
+ // a given selector.
+ eRestyle_SomeDescendants = 1 << 1,
+
+ // Rerun selector matching on the element and all of its descendants.
+ // (Implies eRestyle_ForceDescendants, which ensures that we continue
+ // the restyling process for all descendants, but doesn't cause
+ // selector matching.)
+ eRestyle_Subtree = 1 << 2,
+
+ // Rerun selector matching on all later siblings of the element and
+ // all of their descendants.
+ eRestyle_LaterSiblings = 1 << 3,
+
+ // Replace the style data coming from CSS transitions without updating
+ // any other style data. If a new style context results, update style
+ // contexts on the descendants. (Irrelevant if eRestyle_Self or
+ // eRestyle_Subtree is also set, since those imply a superset of the
+ // work.)
+ eRestyle_CSSTransitions = 1 << 4,
+
+ // Replace the style data coming from CSS animations without updating
+ // any other style data. If a new style context results, update style
+ // contexts on the descendants. (Irrelevant if eRestyle_Self or
+ // eRestyle_Subtree is also set, since those imply a superset of the
+ // work.)
+ eRestyle_CSSAnimations = 1 << 5,
+
+ // Replace the style data coming from SVG animations (SMIL Animations)
+ // without updating any other style data. If a new style context
+ // results, update style contexts on the descendants. (Irrelevant if
+ // eRestyle_Self or eRestyle_Subtree is also set, since those imply a
+ // superset of the work.)
+ eRestyle_SVGAttrAnimations = 1 << 6,
+
+ // Replace the style data coming from inline style without updating
+ // any other style data. If a new style context results, update style
+ // contexts on the descendants. (Irrelevant if eRestyle_Self or
+ // eRestyle_Subtree is also set, since those imply a superset of the
+ // work.) Supported only for element style contexts and not for
+ // pseudo-elements or anonymous boxes, on which it converts to
+ // eRestyle_Self.
+ // If the change is for the advance of a declarative animation, use
+ // the value below instead.
+ eRestyle_StyleAttribute = 1 << 7,
+
+ // Same as eRestyle_StyleAttribute, but for when the change results
+ // from the advance of a declarative animation.
+ eRestyle_StyleAttribute_Animations = 1 << 8,
+
+ // Continue the restyling process to the current frame's children even
+ // if this frame's restyling resulted in no style changes.
+ eRestyle_Force = 1 << 9,
+
+ // Continue the restyling process to all of the current frame's
+ // descendants, even if any frame's restyling resulted in no style
+ // changes. (Implies eRestyle_Force.) Note that this is weaker than
+ // eRestyle_Subtree, which makes us rerun selector matching on all
+ // descendants rather than just continuing the restyling process.
+ eRestyle_ForceDescendants = 1 << 10,
+
+ // Useful unions:
+ eRestyle_AllHintsWithAnimations = eRestyle_CSSTransitions |
+ eRestyle_CSSAnimations |
+ eRestyle_SVGAttrAnimations |
+ eRestyle_StyleAttribute_Animations,
+};
+
+// The functions below need an integral type to cast to to avoid
+// infinite recursion.
+typedef decltype(nsRestyleHint(0) + nsRestyleHint(0)) nsRestyleHint_size_t;
+
+inline constexpr nsRestyleHint operator|(nsRestyleHint aLeft,
+ nsRestyleHint aRight)
+{
+ return nsRestyleHint(nsRestyleHint_size_t(aLeft) |
+ nsRestyleHint_size_t(aRight));
+}
+
+inline constexpr nsRestyleHint operator&(nsRestyleHint aLeft,
+ nsRestyleHint aRight)
+{
+ return nsRestyleHint(nsRestyleHint_size_t(aLeft) &
+ nsRestyleHint_size_t(aRight));
+}
+
+inline nsRestyleHint& operator|=(nsRestyleHint& aLeft, nsRestyleHint aRight)
+{
+ return aLeft = aLeft | aRight;
+}
+
+inline nsRestyleHint& operator&=(nsRestyleHint& aLeft, nsRestyleHint aRight)
+{
+ return aLeft = aLeft & aRight;
+}
+
+inline constexpr nsRestyleHint operator~(nsRestyleHint aArg)
+{
+ return nsRestyleHint(~nsRestyleHint_size_t(aArg));
+}
+
+inline constexpr nsRestyleHint operator^(nsRestyleHint aLeft,
+ nsRestyleHint aRight)
+{
+ return nsRestyleHint(nsRestyleHint_size_t(aLeft) ^
+ nsRestyleHint_size_t(aRight));
+}
+
+inline nsRestyleHint operator^=(nsRestyleHint& aLeft, nsRestyleHint aRight)
+{
+ return aLeft = aLeft ^ aRight;
+}
+
+namespace mozilla {
+
+/**
+ * Additional data used in conjunction with an nsRestyleHint to control the
+ * restyle process.
+ */
+struct RestyleHintData
+{
+ // When eRestyle_SomeDescendants is used, this array contains the selectors
+ // that identify which descendants will be restyled.
+ nsTArray<nsCSSSelector*> mSelectorsForDescendants;
+};
+
+} // namespace mozilla
+
+#endif /* nsChangeHint_h___ */
diff --git a/layout/base/nsCompatibility.h b/layout/base/nsCompatibility.h
new file mode 100644
index 000000000..7cc94b2e5
--- /dev/null
+++ b/layout/base/nsCompatibility.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants for quirks mode, standards mode, and almost standards mode */
+
+#ifndef nsCompatibility_h___
+#define nsCompatibility_h___
+
+enum nsCompatibility {
+ eCompatibility_FullStandards = 1,
+ eCompatibility_AlmostStandards = 2,
+ eCompatibility_NavQuirks = 3
+};
+
+#endif /* nsCompatibility_h___ */
diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp
new file mode 100644
index 000000000..2d998abd0
--- /dev/null
+++ b/layout/base/nsCounterManager.cpp
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of CSS counters (for numbering things) */
+
+#include "nsCounterManager.h"
+
+#include "mozilla/Likely.h"
+#include "mozilla/WritingModes.h"
+#include "nsBulletFrame.h" // legacy location for list style type to text code
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+
+bool
+nsCounterUseNode::InitTextFrame(nsGenConList* aList,
+ nsIFrame* aPseudoFrame, nsIFrame* aTextFrame)
+{
+ nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
+
+ nsCounterList *counterList = static_cast<nsCounterList*>(aList);
+ counterList->Insert(this);
+ bool dirty = counterList->IsDirty();
+ if (!dirty) {
+ if (counterList->IsLast(this)) {
+ Calc(counterList);
+ nsAutoString contentString;
+ GetText(contentString);
+ aTextFrame->GetContent()->SetText(contentString, false);
+ } else {
+ // In all other cases (list already dirty or node not at the end),
+ // just start with an empty string for now and when we recalculate
+ // the list we'll change the value to the right one.
+ counterList->SetDirty();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+CounterStyle*
+nsCounterUseNode::GetCounterStyle()
+{
+ if (!mCounterStyle) {
+ const nsCSSValue& style = mCounterFunction->Item(mAllCounters ? 2 : 1);
+ CounterStyleManager* manager = mPresContext->CounterStyleManager();
+ if (style.GetUnit() == eCSSUnit_Ident) {
+ nsString ident;
+ style.GetStringValue(ident);
+ mCounterStyle = manager->BuildCounterStyle(ident);
+ } else if (style.GetUnit() == eCSSUnit_Symbols) {
+ mCounterStyle = new AnonymousCounterStyle(style.GetArrayValue());
+ } else {
+ NS_NOTREACHED("Unknown counter style");
+ mCounterStyle = CounterStyleManager::GetDecimalStyle();
+ }
+ }
+ return mCounterStyle;
+}
+
+// assign the correct |mValueAfter| value to a node that has been inserted
+// Should be called immediately after calling |Insert|.
+void nsCounterUseNode::Calc(nsCounterList *aList)
+{
+ NS_ASSERTION(!aList->IsDirty(),
+ "Why are we calculating with a dirty list?");
+ mValueAfter = aList->ValueBefore(this);
+}
+
+// assign the correct |mValueAfter| value to a node that has been inserted
+// Should be called immediately after calling |Insert|.
+void nsCounterChangeNode::Calc(nsCounterList *aList)
+{
+ NS_ASSERTION(!aList->IsDirty(),
+ "Why are we calculating with a dirty list?");
+ if (mType == RESET) {
+ mValueAfter = mChangeValue;
+ } else {
+ NS_ASSERTION(mType == INCREMENT, "invalid type");
+ mValueAfter = nsCounterManager::IncrementCounter(aList->ValueBefore(this),
+ mChangeValue);
+ }
+}
+
+// The text that should be displayed for this counter.
+void
+nsCounterUseNode::GetText(nsString& aResult)
+{
+ aResult.Truncate();
+
+ AutoTArray<nsCounterNode*, 8> stack;
+ stack.AppendElement(static_cast<nsCounterNode*>(this));
+
+ if (mAllCounters && mScopeStart)
+ for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart)
+ stack.AppendElement(n->mScopePrev);
+
+ const char16_t* separator;
+ if (mAllCounters)
+ separator = mCounterFunction->Item(1).GetStringBufferValue();
+
+ CounterStyle* style = GetCounterStyle();
+ WritingMode wm = mPseudoFrame ?
+ mPseudoFrame->GetWritingMode() : WritingMode();
+ for (uint32_t i = stack.Length() - 1;; --i) {
+ nsCounterNode *n = stack[i];
+ nsAutoString text;
+ bool isTextRTL;
+ style->GetCounterText(n->mValueAfter, wm, text, isTextRTL);
+ aResult.Append(text);
+ if (i == 0)
+ break;
+ NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized");
+ aResult.Append(separator);
+ }
+}
+
+void
+nsCounterList::SetScope(nsCounterNode *aNode)
+{
+ // This function is responsible for setting |mScopeStart| and
+ // |mScopePrev| (whose purpose is described in nsCounterManager.h).
+ // We do this by starting from the node immediately preceding
+ // |aNode| in content tree order, which is reasonably likely to be
+ // the previous element in our scope (or, for a reset, the previous
+ // element in the containing scope, which is what we want). If
+ // we're not in the same scope that it is, then it's too deep in the
+ // frame tree, so we walk up parent scopes until we find something
+ // appropriate.
+
+ if (aNode == First()) {
+ aNode->mScopeStart = nullptr;
+ aNode->mScopePrev = nullptr;
+ return;
+ }
+
+ // Get the content node for aNode's rendering object's *parent*,
+ // since scope includes siblings, so we want a descendant check on
+ // parents.
+ nsIContent *nodeContent = aNode->mPseudoFrame->GetContent()->GetParent();
+
+ for (nsCounterNode *prev = Prev(aNode), *start;
+ prev; prev = start->mScopePrev) {
+ // If |prev| starts a scope (because it's a real or implied
+ // reset), we want it as the scope start rather than the start
+ // of its enclosing scope. Otherwise, there's no enclosing
+ // scope, so the next thing in prev's scope shares its scope
+ // start.
+ start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart)
+ ? prev : prev->mScopeStart;
+
+ // |startContent| is analogous to |nodeContent| (see above).
+ nsIContent *startContent = start->mPseudoFrame->GetContent()->GetParent();
+ NS_ASSERTION(nodeContent || !startContent,
+ "null check on startContent should be sufficient to "
+ "null check nodeContent as well, since if nodeContent "
+ "is for the root, startContent (which is before it) "
+ "must be too");
+
+ // A reset's outer scope can't be a scope created by a sibling.
+ if (!(aNode->mType == nsCounterNode::RESET &&
+ nodeContent == startContent) &&
+ // everything is inside the root (except the case above,
+ // a second reset on the root)
+ (!startContent ||
+ nsContentUtils::ContentIsDescendantOf(nodeContent,
+ startContent))) {
+ aNode->mScopeStart = start;
+ aNode->mScopePrev = prev;
+ return;
+ }
+ }
+
+ aNode->mScopeStart = nullptr;
+ aNode->mScopePrev = nullptr;
+}
+
+void
+nsCounterList::RecalcAll()
+{
+ mDirty = false;
+
+ for (nsCounterNode* node = First(); node; node = Next(node)) {
+ SetScope(node);
+ node->Calc(this);
+
+ if (node->mType == nsCounterNode::USE) {
+ nsCounterUseNode *useNode = node->UseNode();
+ // Null-check mText, since if the frame constructor isn't
+ // batching, we could end up here while the node is being
+ // constructed.
+ if (useNode->mText) {
+ nsAutoString text;
+ useNode->GetText(text);
+ useNode->mText->SetData(text);
+ }
+ }
+ }
+}
+
+nsCounterManager::nsCounterManager()
+ : mNames()
+{
+}
+
+bool
+nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
+{
+ const nsStyleContent *styleContent = aFrame->StyleContent();
+ if (!styleContent->CounterIncrementCount() &&
+ !styleContent->CounterResetCount())
+ return false;
+
+ // Add in order, resets first, so all the comparisons will be optimized
+ // for addition at the end of the list.
+ int32_t i, i_end;
+ bool dirty = false;
+ for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i)
+ dirty |= AddResetOrIncrement(aFrame, i, styleContent->CounterResetAt(i),
+ nsCounterChangeNode::RESET);
+ for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i)
+ dirty |=
+ AddResetOrIncrement(aFrame, i, styleContent->CounterIncrementAt(i),
+ nsCounterChangeNode::INCREMENT);
+ return dirty;
+}
+
+bool
+nsCounterManager::AddResetOrIncrement(nsIFrame* aFrame, int32_t aIndex,
+ const nsStyleCounterData& aCounterData,
+ nsCounterNode::Type aType)
+{
+ nsCounterChangeNode* node =
+ new nsCounterChangeNode(aFrame, aType, aCounterData.mValue, aIndex);
+
+ nsCounterList* counterList = CounterListFor(aCounterData.mCounter);
+
+ counterList->Insert(node);
+ if (!counterList->IsLast(node)) {
+ // Tell the caller it's responsible for recalculating the entire
+ // list.
+ counterList->SetDirty();
+ return true;
+ }
+
+ // Don't call Calc() if the list is already dirty -- it'll be recalculated
+ // anyway, and trying to calculate with a dirty list doesn't work.
+ if (MOZ_LIKELY(!counterList->IsDirty())) {
+ node->Calc(counterList);
+ }
+ return false;
+}
+
+nsCounterList*
+nsCounterManager::CounterListFor(const nsSubstring& aCounterName)
+{
+ // XXX Why doesn't nsTHashtable provide an API that allows us to use
+ // get/put in one hashtable lookup?
+ nsCounterList *counterList;
+ if (!mNames.Get(aCounterName, &counterList)) {
+ counterList = new nsCounterList();
+ mNames.Put(aCounterName, counterList);
+ }
+ return counterList;
+}
+
+void
+nsCounterManager::RecalcAll()
+{
+ for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
+ nsCounterList* list = iter.UserData();
+ if (list->IsDirty()) {
+ list->RecalcAll();
+ }
+ }
+}
+
+void
+nsCounterManager::SetAllCounterStylesDirty()
+{
+ for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
+ nsCounterList* list = iter.UserData();
+ bool changed = false;
+
+ for (nsCounterNode* node = list->First(); node; node = list->Next(node)) {
+ if (node->mType == nsCounterNode::USE) {
+ node->UseNode()->SetCounterStyleDirty();
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ list->SetDirty();
+ }
+ }
+}
+
+bool
+nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
+{
+ bool destroyedAny = false;
+ for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
+ nsCounterList* list = iter.UserData();
+ if (list->DestroyNodesFor(aFrame)) {
+ destroyedAny = true;
+ list->SetDirty();
+ }
+ }
+ return destroyedAny;
+}
+
+#ifdef DEBUG
+void
+nsCounterManager::Dump()
+{
+ printf("\n\nCounter Manager Lists:\n");
+ for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
+ printf("Counter named \"%s\":\n",
+ NS_ConvertUTF16toUTF8(iter.Key()).get());
+
+ nsCounterList* list = iter.UserData();
+ int32_t i = 0;
+ for (nsCounterNode* node = list->First(); node; node = list->Next(node)) {
+ const char* types[] = { "RESET", "INCREMENT", "USE" };
+ printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
+ " scope-start=%p scope-prev=%p",
+ i++, (void*)node, (void*)node->mPseudoFrame,
+ node->mContentIndex, types[node->mType],
+ node->mValueAfter, (void*)node->mScopeStart,
+ (void*)node->mScopePrev);
+ if (node->mType == nsCounterNode::USE) {
+ nsAutoString text;
+ node->UseNode()->GetText(text);
+ printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
+ }
+ printf("\n");
+ }
+ }
+ printf("\n\n");
+}
+#endif
diff --git a/layout/base/nsCounterManager.h b/layout/base/nsCounterManager.h
new file mode 100644
index 000000000..20d30c865
--- /dev/null
+++ b/layout/base/nsCounterManager.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ai:sw=4:ts=4:et:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of CSS counters (for numbering things) */
+
+#ifndef nsCounterManager_h_
+#define nsCounterManager_h_
+
+#include "mozilla/Attributes.h"
+#include "nsGenConList.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Likely.h"
+#include "CounterStyleManager.h"
+
+class nsCounterList;
+struct nsCounterUseNode;
+struct nsCounterChangeNode;
+
+struct nsCounterNode : public nsGenConNode {
+ enum Type {
+ RESET, // a "counter number" pair in 'counter-reset'
+ INCREMENT, // a "counter number" pair in 'counter-increment'
+ USE // counter() or counters() in 'content'
+ };
+
+ Type mType;
+
+ // Counter value after this node
+ int32_t mValueAfter;
+
+ // mScopeStart points to the node (usually a RESET, but not in the
+ // case of an implied 'counter-reset') that created the scope for
+ // this element (for a RESET, its outer scope, i.e., the one it is
+ // inside rather than the one it creates).
+
+ // May be null for all types, but only when mScopePrev is also null.
+ // Being null for a non-RESET means that it is an implied
+ // 'counter-reset'. Being null for a RESET means it has no outer
+ // scope.
+ nsCounterNode *mScopeStart;
+
+ // mScopePrev points to the previous node that is in the same scope,
+ // or for a RESET, the previous node in the scope outside of the
+ // reset.
+
+ // May be null for all types, but only when mScopeStart is also
+ // null. Following the mScopePrev links will eventually lead to
+ // mScopeStart. Being null for a non-RESET means that it is an
+ // implied 'counter-reset'. Being null for a RESET means it has no
+ // outer scope.
+ nsCounterNode *mScopePrev;
+
+ inline nsCounterUseNode* UseNode();
+ inline nsCounterChangeNode* ChangeNode();
+
+ // For RESET and INCREMENT nodes, aPseudoFrame need not be a
+ // pseudo-element, and aContentIndex represents the index within the
+ // 'counter-reset' or 'counter-increment' property instead of within
+ // the 'content' property but offset to ensure that (reset,
+ // increment, use) sort in that order. (This slight weirdness
+ // allows sharing a lot of code with 'quotes'.)
+ nsCounterNode(int32_t aContentIndex, Type aType)
+ : nsGenConNode(aContentIndex)
+ , mType(aType)
+ , mValueAfter(0)
+ , mScopeStart(nullptr)
+ , mScopePrev(nullptr)
+ {
+ }
+
+ // to avoid virtual function calls in the common case
+ inline void Calc(nsCounterList* aList);
+};
+
+struct nsCounterUseNode : public nsCounterNode {
+ // The same structure passed through the style system: an array
+ // containing the values in the counter() or counters() in the order
+ // given in the CSS spec.
+ RefPtr<nsCSSValue::Array> mCounterFunction;
+
+ nsPresContext* mPresContext;
+ RefPtr<mozilla::CounterStyle> mCounterStyle;
+
+ // false for counter(), true for counters()
+ bool mAllCounters;
+
+ // args go directly to member variables here and of nsGenConNode
+ nsCounterUseNode(nsPresContext* aPresContext,
+ nsCSSValue::Array* aCounterFunction,
+ uint32_t aContentIndex, bool aAllCounters)
+ : nsCounterNode(aContentIndex, USE)
+ , mCounterFunction(aCounterFunction)
+ , mPresContext(aPresContext)
+ , mCounterStyle(nullptr)
+ , mAllCounters(aAllCounters)
+ {
+ NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
+ }
+
+ virtual bool InitTextFrame(nsGenConList* aList,
+ nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) override;
+
+ mozilla::CounterStyle* GetCounterStyle();
+ void SetCounterStyleDirty()
+ {
+ mCounterStyle = nullptr;
+ }
+
+ // assign the correct |mValueAfter| value to a node that has been inserted
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsCounterList* aList);
+
+ // The text that should be displayed for this counter.
+ void GetText(nsString& aResult);
+};
+
+struct nsCounterChangeNode : public nsCounterNode {
+ int32_t mChangeValue; // the numeric value of the increment or reset
+
+ // |aPseudoFrame| is not necessarily a pseudo-element's frame, but
+ // since it is for every other subclass of nsGenConNode, we follow
+ // the naming convention here.
+ // |aPropIndex| is the index of the value within the list in the
+ // 'counter-increment' or 'counter-reset' property.
+ nsCounterChangeNode(nsIFrame* aPseudoFrame,
+ nsCounterNode::Type aChangeType,
+ int32_t aChangeValue,
+ int32_t aPropIndex)
+ : nsCounterNode(// Fake a content index for resets and increments
+ // that comes before all the real content, with
+ // the resets first, in order, and then the increments.
+ aPropIndex + (aChangeType == RESET
+ ? (INT32_MIN)
+ : (INT32_MIN / 2)),
+ aChangeType)
+ , mChangeValue(aChangeValue)
+ {
+ NS_ASSERTION(aPropIndex >= 0, "out of range");
+ NS_ASSERTION(aChangeType == INCREMENT || aChangeType == RESET,
+ "bad type");
+ mPseudoFrame = aPseudoFrame;
+ CheckFrameAssertions();
+ }
+
+ // assign the correct |mValueAfter| value to a node that has been inserted
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsCounterList* aList);
+};
+
+inline nsCounterUseNode* nsCounterNode::UseNode()
+{
+ NS_ASSERTION(mType == USE, "wrong type");
+ return static_cast<nsCounterUseNode*>(this);
+}
+
+inline nsCounterChangeNode* nsCounterNode::ChangeNode()
+{
+ NS_ASSERTION(mType == INCREMENT || mType == RESET, "wrong type");
+ return static_cast<nsCounterChangeNode*>(this);
+}
+
+inline void nsCounterNode::Calc(nsCounterList* aList)
+{
+ if (mType == USE)
+ UseNode()->Calc(aList);
+ else
+ ChangeNode()->Calc(aList);
+}
+
+class nsCounterList : public nsGenConList {
+public:
+ nsCounterList() : nsGenConList(),
+ mDirty(false)
+ {}
+
+ void Insert(nsCounterNode* aNode) {
+ nsGenConList::Insert(aNode);
+ // Don't SetScope if we're dirty -- we'll reset all the scopes anyway,
+ // and we can't usefully compute scopes right now.
+ if (MOZ_LIKELY(!IsDirty())) {
+ SetScope(aNode);
+ }
+ }
+
+ nsCounterNode* First() {
+ return static_cast<nsCounterNode*>(mList.getFirst());
+ }
+
+ static nsCounterNode* Next(nsCounterNode* aNode) {
+ return static_cast<nsCounterNode*>(nsGenConList::Next(aNode));
+ }
+ static nsCounterNode* Prev(nsCounterNode* aNode) {
+ return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode));
+ }
+
+ static int32_t ValueBefore(nsCounterNode* aNode) {
+ return aNode->mScopePrev ? aNode->mScopePrev->mValueAfter : 0;
+ }
+
+ // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev|
+ void SetScope(nsCounterNode *aNode);
+
+ // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for
+ // all nodes and update text in text content nodes.
+ void RecalcAll();
+
+ bool IsDirty() { return mDirty; }
+ void SetDirty() { mDirty = true; }
+
+private:
+ bool mDirty;
+};
+
+/**
+ * The counter manager maintains an |nsCounterList| for each named
+ * counter to keep track of all scopes with that name.
+ */
+class nsCounterManager {
+public:
+ nsCounterManager();
+ // Returns true if dirty
+ bool AddCounterResetsAndIncrements(nsIFrame *aFrame);
+
+ // Gets the appropriate counter list, creating it if necessary.
+ // Guaranteed to return non-null. (Uses an infallible hashtable API.)
+ nsCounterList* CounterListFor(const nsSubstring& aCounterName);
+
+ // Clean up data in any dirty counter lists.
+ void RecalcAll();
+
+ // Set all counter styles dirty
+ void SetAllCounterStylesDirty();
+
+ // Destroy nodes for the frame in any lists, and return whether any
+ // nodes were destroyed.
+ bool DestroyNodesFor(nsIFrame *aFrame);
+
+ // Clear all data.
+ void Clear() { mNames.Clear(); }
+
+#ifdef DEBUG
+ void Dump();
+#endif
+
+ static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement)
+ {
+ // Addition of unsigned values is defined to be arithmetic
+ // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4);
+ // addition of signed values is undefined (and clang does
+ // something very strange if we use it here). Likewise integral
+ // conversion from signed to unsigned is also defined as modulo
+ // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion
+ // from unsigned to signed is however undefined (ibid., clause 3),
+ // but to do what we want we must nonetheless depend on that
+ // small piece of undefined behavior.
+ int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement));
+ // The CSS Working Group resolved that a counter-increment that
+ // exceeds internal limits should not increment at all.
+ // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html
+ // (This means, for example, that if aIncrement is 5, the
+ // counter will get stuck at the largest multiple of 5 less than
+ // the maximum 32-bit integer.)
+ if ((aIncrement > 0) != (newValue > aOldValue)) {
+ newValue = aOldValue;
+ }
+ return newValue;
+ }
+
+private:
+ // for |AddCounterResetsAndIncrements| only
+ bool AddResetOrIncrement(nsIFrame* aFrame, int32_t aIndex,
+ const nsStyleCounterData& aCounterData,
+ nsCounterNode::Type aType);
+
+ nsClassHashtable<nsStringHashKey, nsCounterList> mNames;
+};
+
+#endif /* nsCounterManager_h_ */
diff --git a/layout/base/nsDisplayItemTypes.h b/layout/base/nsDisplayItemTypes.h
new file mode 100644
index 000000000..e899245d0
--- /dev/null
+++ b/layout/base/nsDisplayItemTypes.h
@@ -0,0 +1,72 @@
+// IWYU pragma: private, include "nsDisplayList.h"
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/**
+ * It's useful to be able to dynamically check the type of certain items.
+ * Every subclass of nsDisplayItem must have a new type added here for the purposes
+ * of easy comparison and matching of items in different display lists.
+ *
+ * This is #included inside nsDisplayItem.
+ */
+
+
+enum Type {
+ TYPE_ZERO = 0, /** Spacer so that the first item starts at 1 */
+
+#define DECLARE_DISPLAY_ITEM_TYPE(name) TYPE_##name,
+#define DECLARE_DISPLAY_ITEM_TYPE_FLAGS(name,flags) TYPE_##name,
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+#undef DECLARE_DISPLAY_ITEM_TYPE_FLAGS
+
+ TYPE_MAX
+};
+
+enum {
+ // Number of bits needed to represent all types
+ TYPE_BITS = 8
+};
+
+enum DisplayItemFlags {
+ TYPE_RENDERS_NO_IMAGES = 1 << 0
+};
+
+static const char* DisplayItemTypeName(Type aType)
+{
+ switch (aType) {
+#define DECLARE_DISPLAY_ITEM_TYPE(name) case TYPE_##name: return #name;
+#define DECLARE_DISPLAY_ITEM_TYPE_FLAGS(name,flags) case TYPE_##name: return #name;
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+#undef DECLARE_DISPLAY_ITEM_TYPE_FLAGS
+
+ default: return "TYPE_UNKNOWN";
+ }
+}
+
+static uint8_t GetDisplayItemFlagsForType(Type aType)
+{
+ static const uint8_t flags[TYPE_MAX] = {
+ 0
+#define DECLARE_DISPLAY_ITEM_TYPE(name) ,0
+#define DECLARE_DISPLAY_ITEM_TYPE_FLAGS(name,flags) ,flags
+#include "nsDisplayItemTypesList.h"
+#undef DECLARE_DISPLAY_ITEM_TYPE
+#undef DECLARE_DISPLAY_ITEM_TYPE_FLAGS
+ };
+
+ return flags[aType];
+}
+
+static Type GetDisplayItemTypeFromKey(uint32_t aDisplayItemKey)
+{
+ static const uint32_t typeMask = (1 << TYPE_BITS) - 1;
+ Type type = static_cast<Type>(aDisplayItemKey & typeMask);
+ NS_ASSERTION(type > TYPE_ZERO && type < TYPE_MAX, "Invalid display item type!");
+ return type;
+}
diff --git a/layout/base/nsDisplayItemTypesList.h b/layout/base/nsDisplayItemTypesList.h
new file mode 100644
index 000000000..9865395a7
--- /dev/null
+++ b/layout/base/nsDisplayItemTypesList.h
@@ -0,0 +1,100 @@
+// IWYU pragma: private, include "nsDisplayList.h"
+DECLARE_DISPLAY_ITEM_TYPE(ALT_FEEDBACK)
+DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(THEMED_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE_FLAGS(BACKGROUND_COLOR,TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER)
+DECLARE_DISPLAY_ITEM_TYPE(BLEND_MODE)
+DECLARE_DISPLAY_ITEM_TYPE(BORDER)
+DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_OUTER)
+DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_INNER)
+DECLARE_DISPLAY_ITEM_TYPE(BULLET)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BORDER_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BOX_SHADOW_OUTER)
+DECLARE_DISPLAY_ITEM_TYPE(BUTTON_FOREGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_COLOR)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_THEMED_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_IMAGE)
+DECLARE_DISPLAY_ITEM_TYPE(CANVAS_FOCUS)
+DECLARE_DISPLAY_ITEM_TYPE(CARET)
+DECLARE_DISPLAY_ITEM_TYPE(CHECKED_CHECKBOX)
+DECLARE_DISPLAY_ITEM_TYPE(CHECKED_RADIOBUTTON)
+DECLARE_DISPLAY_ITEM_TYPE(CLEAR_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE)
+DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS)
+DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER)
+DECLARE_DISPLAY_ITEM_TYPE(LAYER_EVENT_REGIONS)
+DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(FIXED_POSITION)
+DECLARE_DISPLAY_ITEM_TYPE(STICKY_POSITION)
+DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER)
+DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK)
+DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER)
+DECLARE_DISPLAY_ITEM_TYPE(IMAGE)
+DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS)
+DECLARE_DISPLAY_ITEM_TYPE(OPACITY)
+DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER)
+DECLARE_DISPLAY_ITEM_TYPE(OUTLINE)
+DECLARE_DISPLAY_ITEM_TYPE(OWN_LAYER)
+DECLARE_DISPLAY_ITEM_TYPE(PLUGIN)
+DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_READBACK)
+DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_VIDEO)
+DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN)
+DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING)
+DECLARE_DISPLAY_ITEM_TYPE(REMOTE)
+DECLARE_DISPLAY_ITEM_TYPE(RESOLUTION)
+DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER)
+DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY)
+DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR)
+DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION)
+DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT)
+DECLARE_DISPLAY_ITEM_TYPE(MASK)
+DECLARE_DISPLAY_ITEM_TYPE(FILTER)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_PATH_GEOMETRY)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_GROUP_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TEXT)
+DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW)
+DECLARE_DISPLAY_ITEM_TYPE_FLAGS(TRANSFORM,TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE_FLAGS(PERSPECTIVE,TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(VIDEO)
+DECLARE_DISPLAY_ITEM_TYPE(WRAP_LIST)
+DECLARE_DISPLAY_ITEM_TYPE(ZOOM)
+
+#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
+DECLARE_DISPLAY_ITEM_TYPE(REFLOW_COUNT)
+#endif
+
+#ifdef MOZ_XUL
+DECLARE_DISPLAY_ITEM_TYPE(XUL_EVENT_REDIRECTOR)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_GROUP_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_IMAGE)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TEXT_BOX)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_BODY)
+DECLARE_DISPLAY_ITEM_TYPE(XUL_TREE_COL_SPLITTER_TARGET)
+#ifdef DEBUG_LAYOUT
+DECLARE_DISPLAY_ITEM_TYPE(XUL_DEBUG)
+#endif
+#endif
+
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_BAR)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_FOREGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_ERROR)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_MENCLOSE_NOTATION)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_SELECTION_RECT)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_SLASH)
+#ifdef DEBUG
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_BOUNDING_METRICS)
+DECLARE_DISPLAY_ITEM_TYPE(MATHML_CHAR_DEBUG)
+
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_BORDER)
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_IMAGE_MAP)
+DECLARE_DISPLAY_ITEM_TYPE(DEBUG_PLACEHOLDER)
+DECLARE_DISPLAY_ITEM_TYPE(EVENT_TARGET_BORDER)
+#endif
diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp
new file mode 100644
index 000000000..8035269e3
--- /dev/null
+++ b/layout/base/nsDisplayList.cpp
@@ -0,0 +1,7553 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/*
+ * structures that represent things to be painted (ordered in z-order),
+ * used during painting and hit testing
+ */
+
+#include "nsDisplayList.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <limits>
+
+#include "gfxUtils.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/layers/PLayerTransaction.h"
+#include "nsCSSRendering.h"
+#include "nsRenderingContext.h"
+#include "nsISelectionController.h"
+#include "nsIPresShell.h"
+#include "nsRegion.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "gfxMatrix.h"
+#include "gfxPrefs.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsThemeConstants.h"
+#include "LayerTreeInvalidation.h"
+
+#include "imgIContainer.h"
+#include "BasicLayers.h"
+#include "nsBoxFrame.h"
+#include "nsViewportFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsSVGEffects.h"
+#include "nsSVGElement.h"
+#include "nsSVGClipPathFrame.h"
+#include "GeckoProfiler.h"
+#include "nsViewManager.h"
+#include "ImageLayers.h"
+#include "ImageContainer.h"
+#include "nsCanvasFrame.h"
+#include "StickyScrollContainer.h"
+#include "mozilla/AnimationPerformanceWarning.h"
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "ActiveLayerTracker.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "UnitTransforms.h"
+#include "LayersLogging.h"
+#include "FrameLayerBuilder.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/RestyleManager.h"
+#include "nsCaret.h"
+#include "nsISelection.h"
+#include "nsDOMTokenList.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "nsCSSProps.h"
+#include "nsPluginFrame.h"
+#include "DisplayItemScrollClip.h"
+#include "nsSVGMaskFrame.h"
+
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount().
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+using namespace mozilla::gfx;
+
+typedef FrameMetrics::ViewID ViewID;
+typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
+
+#ifdef DEBUG
+static bool
+SpammyLayoutWarningsEnabled()
+{
+ static bool sValue = false;
+ static bool sValueInitialized = false;
+
+ if (!sValueInitialized) {
+ Preferences::GetBool("layout.spammy_warnings.enabled", &sValue);
+ sValueInitialized = true;
+ }
+
+ return sValue;
+}
+#endif
+
+void*
+AnimatedGeometryRoot::operator new(size_t aSize, nsDisplayListBuilder* aBuilder)
+{
+ return aBuilder->Allocate(aSize);
+}
+
+static inline CSSAngle
+MakeCSSAngle(const nsCSSValue& aValue)
+{
+ return CSSAngle(aValue.GetAngleValue(), aValue.GetUnit());
+}
+
+static void AddTransformFunctions(nsCSSValueList* aList,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ TransformReferenceBox& aRefBox,
+ InfallibleTArray<TransformFunction>& aFunctions)
+{
+ if (aList->mValue.GetUnit() == eCSSUnit_None) {
+ return;
+ }
+
+ for (const nsCSSValueList* curr = aList; curr; curr = curr->mNext) {
+ const nsCSSValue& currElem = curr->mValue;
+ NS_ASSERTION(currElem.GetUnit() == eCSSUnit_Function,
+ "Stream should consist solely of functions!");
+ nsCSSValue::Array* array = currElem.GetArrayValue();
+ RuleNodeCacheConditions conditions;
+ switch (nsStyleTransformMatrix::TransformFunctionOf(array)) {
+ case eCSSKeyword_rotatex:
+ {
+ CSSAngle theta = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(RotationX(theta));
+ break;
+ }
+ case eCSSKeyword_rotatey:
+ {
+ CSSAngle theta = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(RotationY(theta));
+ break;
+ }
+ case eCSSKeyword_rotatez:
+ {
+ CSSAngle theta = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(RotationZ(theta));
+ break;
+ }
+ case eCSSKeyword_rotate:
+ {
+ CSSAngle theta = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(Rotation(theta));
+ break;
+ }
+ case eCSSKeyword_rotate3d:
+ {
+ double x = array->Item(1).GetFloatValue();
+ double y = array->Item(2).GetFloatValue();
+ double z = array->Item(3).GetFloatValue();
+ CSSAngle theta = MakeCSSAngle(array->Item(4));
+ aFunctions.AppendElement(Rotation3D(x, y, z, theta));
+ break;
+ }
+ case eCSSKeyword_scalex:
+ {
+ double x = array->Item(1).GetFloatValue();
+ aFunctions.AppendElement(Scale(x, 1, 1));
+ break;
+ }
+ case eCSSKeyword_scaley:
+ {
+ double y = array->Item(1).GetFloatValue();
+ aFunctions.AppendElement(Scale(1, y, 1));
+ break;
+ }
+ case eCSSKeyword_scalez:
+ {
+ double z = array->Item(1).GetFloatValue();
+ aFunctions.AppendElement(Scale(1, 1, z));
+ break;
+ }
+ case eCSSKeyword_scale:
+ {
+ double x = array->Item(1).GetFloatValue();
+ // scale(x) is shorthand for scale(x, x);
+ double y = array->Count() == 2 ? x : array->Item(2).GetFloatValue();
+ aFunctions.AppendElement(Scale(x, y, 1));
+ break;
+ }
+ case eCSSKeyword_scale3d:
+ {
+ double x = array->Item(1).GetFloatValue();
+ double y = array->Item(2).GetFloatValue();
+ double z = array->Item(3).GetFloatValue();
+ aFunctions.AppendElement(Scale(x, y, z));
+ break;
+ }
+ case eCSSKeyword_translatex:
+ {
+ double x = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(1), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ aFunctions.AppendElement(Translation(x, 0, 0));
+ break;
+ }
+ case eCSSKeyword_translatey:
+ {
+ double y = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(1), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ aFunctions.AppendElement(Translation(0, y, 0));
+ break;
+ }
+ case eCSSKeyword_translatez:
+ {
+ double z = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(1), aContext, aPresContext, conditions,
+ nullptr);
+ aFunctions.AppendElement(Translation(0, 0, z));
+ break;
+ }
+ case eCSSKeyword_translate:
+ {
+ double x = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(1), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ // translate(x) is shorthand for translate(x, 0)
+ double y = 0;
+ if (array->Count() == 3) {
+ y = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(2), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ }
+ aFunctions.AppendElement(Translation(x, y, 0));
+ break;
+ }
+ case eCSSKeyword_translate3d:
+ {
+ double x = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(1), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ double y = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(2), aContext, aPresContext, conditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ double z = nsStyleTransformMatrix::ProcessTranslatePart(
+ array->Item(3), aContext, aPresContext, conditions,
+ nullptr);
+
+ aFunctions.AppendElement(Translation(x, y, z));
+ break;
+ }
+ case eCSSKeyword_skewx:
+ {
+ CSSAngle x = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(SkewX(x));
+ break;
+ }
+ case eCSSKeyword_skewy:
+ {
+ CSSAngle y = MakeCSSAngle(array->Item(1));
+ aFunctions.AppendElement(SkewY(y));
+ break;
+ }
+ case eCSSKeyword_skew:
+ {
+ CSSAngle x = MakeCSSAngle(array->Item(1));
+ // skew(x) is shorthand for skew(x, 0)
+ CSSAngle y(0.0f, eCSSUnit_Degree);
+ if (array->Count() == 3) {
+ y = MakeCSSAngle(array->Item(2));
+ }
+ aFunctions.AppendElement(Skew(x, y));
+ break;
+ }
+ case eCSSKeyword_matrix:
+ {
+ gfx::Matrix4x4 matrix;
+ matrix._11 = array->Item(1).GetFloatValue();
+ matrix._12 = array->Item(2).GetFloatValue();
+ matrix._13 = 0;
+ matrix._14 = 0;
+ matrix._21 = array->Item(3).GetFloatValue();
+ matrix._22 = array->Item(4).GetFloatValue();
+ matrix._23 = 0;
+ matrix._24 = 0;
+ matrix._31 = 0;
+ matrix._32 = 0;
+ matrix._33 = 1;
+ matrix._34 = 0;
+ matrix._41 = array->Item(5).GetFloatValue();
+ matrix._42 = array->Item(6).GetFloatValue();
+ matrix._43 = 0;
+ matrix._44 = 1;
+ aFunctions.AppendElement(TransformMatrix(matrix));
+ break;
+ }
+ case eCSSKeyword_matrix3d:
+ {
+ gfx::Matrix4x4 matrix;
+ matrix._11 = array->Item(1).GetFloatValue();
+ matrix._12 = array->Item(2).GetFloatValue();
+ matrix._13 = array->Item(3).GetFloatValue();
+ matrix._14 = array->Item(4).GetFloatValue();
+ matrix._21 = array->Item(5).GetFloatValue();
+ matrix._22 = array->Item(6).GetFloatValue();
+ matrix._23 = array->Item(7).GetFloatValue();
+ matrix._24 = array->Item(8).GetFloatValue();
+ matrix._31 = array->Item(9).GetFloatValue();
+ matrix._32 = array->Item(10).GetFloatValue();
+ matrix._33 = array->Item(11).GetFloatValue();
+ matrix._34 = array->Item(12).GetFloatValue();
+ matrix._41 = array->Item(13).GetFloatValue();
+ matrix._42 = array->Item(14).GetFloatValue();
+ matrix._43 = array->Item(15).GetFloatValue();
+ matrix._44 = array->Item(16).GetFloatValue();
+ aFunctions.AppendElement(TransformMatrix(matrix));
+ break;
+ }
+ case eCSSKeyword_interpolatematrix:
+ {
+ bool dummy;
+ Matrix4x4 matrix;
+ nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, array,
+ aContext,
+ aPresContext,
+ conditions,
+ aRefBox,
+ &dummy);
+ aFunctions.AppendElement(TransformMatrix(matrix));
+ break;
+ }
+ case eCSSKeyword_perspective:
+ {
+ aFunctions.AppendElement(Perspective(array->Item(1).GetFloatValue()));
+ break;
+ }
+ default:
+ NS_ERROR("Function not handled yet!");
+ }
+ }
+}
+
+static TimingFunction
+ToTimingFunction(const Maybe<ComputedTimingFunction>& aCTF)
+{
+ if (aCTF.isNothing()) {
+ return TimingFunction(null_t());
+ }
+
+ if (aCTF->HasSpline()) {
+ const nsSMILKeySpline* spline = aCTF->GetFunction();
+ return TimingFunction(CubicBezierFunction(spline->X1(), spline->Y1(),
+ spline->X2(), spline->Y2()));
+ }
+
+ uint32_t type = aCTF->GetType() == nsTimingFunction::Type::StepStart ? 1 : 2;
+ return TimingFunction(StepFunction(aCTF->GetSteps(), type));
+}
+
+static void
+AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
+ dom::Animation* aAnimation, Layer* aLayer,
+ AnimationData& aData, bool aPending)
+{
+ MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
+ MOZ_ASSERT(aAnimation->GetEffect(),
+ "Should not be adding an animation without an effect");
+ MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() ||
+ (aAnimation->GetTimeline() &&
+ aAnimation->GetTimeline()->TracksWallclockTime()),
+ "Animation should either have a resolved start time or "
+ "a timeline that tracks wallclock time");
+ nsStyleContext* styleContext = aFrame->StyleContext();
+ nsPresContext* presContext = aFrame->PresContext();
+ TransformReferenceBox refBox(aFrame);
+
+ layers::Animation* animation =
+ aPending ?
+ aLayer->AddAnimationForNextTransaction() :
+ aLayer->AddAnimation();
+
+ const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming();
+
+ // If we are starting a new transition that replaces an existing transition
+ // running on the compositor, it is possible that the animation on the
+ // compositor will have advanced ahead of the main thread. If we use as
+ // the starting point of the new transition, the current value of the
+ // replaced transition as calculated on the main thread using the refresh
+ // driver time, the new transition will jump when it starts. Instead, we
+ // re-calculate the starting point of the new transition by applying the
+ // current TimeStamp to the parameters of the replaced transition.
+ //
+ // We need to do this here, rather than when we generate the new transition,
+ // since after generating the new transition other requestAnimationFrame
+ // callbacks may run that introduce further lag between the main thread and
+ // the compositor.
+ if (aAnimation->AsCSSTransition() &&
+ aAnimation->GetEffect() &&
+ aAnimation->GetEffect()->AsTransition()) {
+ // We update startValue from the replaced transition only if the effect is
+ // an ElementPropertyTransition.
+ aAnimation->GetEffect()->AsTransition()->
+ UpdateStartValueFromReplacedTransition();
+ }
+
+ const ComputedTiming computedTiming =
+ aAnimation->GetEffect()->GetComputedTiming();
+ Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
+ animation->startTime() = startTime.IsNull()
+ ? TimeStamp()
+ : aAnimation->GetTimeline()->
+ ToTimeStamp(startTime.Value());
+ animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value()
+ - timing.mDelay;
+ animation->delay() = timing.mDelay;
+ animation->duration() = computedTiming.mDuration;
+ animation->iterations() = computedTiming.mIterations;
+ animation->iterationStart() = computedTiming.mIterationStart;
+ animation->direction() = static_cast<uint8_t>(timing.mDirection);
+ animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill);
+ animation->property() = aProperty.mProperty;
+ animation->playbackRate() = aAnimation->PlaybackRate();
+ animation->data() = aData;
+ animation->easingFunction() = ToTimingFunction(timing.mFunction);
+ animation->iterationComposite() =
+ static_cast<uint8_t>(aAnimation->GetEffect()->
+ AsKeyframeEffect()->IterationComposite());
+
+ for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
+ const AnimationPropertySegment& segment = aProperty.mSegments[segIdx];
+
+ AnimationSegment* animSegment = animation->segments().AppendElement();
+ if (aProperty.mProperty == eCSSProperty_transform) {
+ animSegment->startState() = InfallibleTArray<TransformFunction>();
+ animSegment->endState() = InfallibleTArray<TransformFunction>();
+
+ nsCSSValueSharedList* list =
+ segment.mFromValue.GetCSSValueSharedListValue();
+ AddTransformFunctions(list->mHead, styleContext, presContext, refBox,
+ animSegment->startState().get_ArrayOfTransformFunction());
+
+ list = segment.mToValue.GetCSSValueSharedListValue();
+ AddTransformFunctions(list->mHead, styleContext, presContext, refBox,
+ animSegment->endState().get_ArrayOfTransformFunction());
+ } else if (aProperty.mProperty == eCSSProperty_opacity) {
+ animSegment->startState() = segment.mFromValue.GetFloatValue();
+ animSegment->endState() = segment.mToValue.GetFloatValue();
+ }
+
+ animSegment->startPortion() = segment.mFromKey;
+ animSegment->endPortion() = segment.mToKey;
+ animSegment->sampleFn() = ToTimingFunction(segment.mTimingFunction);
+ }
+}
+
+static void
+AddAnimationsForProperty(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ nsTArray<RefPtr<dom::Animation>>& aAnimations,
+ Layer* aLayer, AnimationData& aData,
+ bool aPending)
+{
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "inconsistent property flags");
+
+ DebugOnly<EffectSet*> effects = EffectSet::GetEffectSet(aFrame);
+ MOZ_ASSERT(effects);
+
+ // Add from first to last (since last overrides)
+ for (size_t animIdx = 0; animIdx < aAnimations.Length(); animIdx++) {
+ dom::Animation* anim = aAnimations[animIdx];
+ if (!anim->IsPlayableOnCompositor()) {
+ continue;
+ }
+
+ dom::KeyframeEffectReadOnly* keyframeEffect =
+ anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
+ MOZ_ASSERT(keyframeEffect,
+ "A playing animation should have a keyframe effect");
+ const AnimationProperty* property =
+ keyframeEffect->GetEffectiveAnimationOfProperty(aProperty);
+ if (!property) {
+ continue;
+ }
+
+ // Note that if the property is overridden by !important rules,
+ // GetEffectiveAnimationOfProperty returns null instead.
+ // This is what we want, since if we have animations overridden by
+ // !important rules, we don't want to send them to the compositor.
+ MOZ_ASSERT(anim->CascadeLevel() !=
+ EffectCompositor::CascadeLevel::Animations ||
+ !effects->PropertiesWithImportantRules()
+ .HasProperty(aProperty),
+ "GetEffectiveAnimationOfProperty already tested the property "
+ "is not overridden by !important rules");
+
+ // Don't add animations that are pending if their timeline does not
+ // track wallclock time. This is because any pending animations on layers
+ // will have their start time updated with the current wallclock time.
+ // If we can't convert that wallclock time back to an equivalent timeline
+ // time, we won't be able to update the content animation and it will end
+ // up being out of sync with the layer animation.
+ //
+ // Currently this only happens when the timeline is driven by a refresh
+ // driver under test control. In this case, the next time the refresh
+ // driver is advanced it will trigger any pending animations.
+ if (anim->PlayState() == AnimationPlayState::Pending &&
+ (!anim->GetTimeline() ||
+ !anim->GetTimeline()->TracksWallclockTime())) {
+ continue;
+ }
+
+ AddAnimationForProperty(aFrame, *property, anim, aLayer, aData, aPending);
+ keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
+ }
+}
+
+static bool
+GenerateAndPushTextMask(nsIFrame* aFrame, nsRenderingContext* aContext,
+ const nsRect& aFillRect, nsDisplayListBuilder* aBuilder)
+{
+ if (aBuilder->IsForGenerateGlyphMask() ||
+ aBuilder->IsForPaintingSelectionBG()) {
+ return false;
+ }
+
+ // The main function of enabling background-clip:text property value.
+ // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call
+ // this function to
+ // 1. Paint background color of the selection text if any.
+ // 2. Generate a mask by all descendant text frames
+ // 3. Push the generated mask into aContext.
+ //
+ // TBD: we actually generate display list of aFrame twice here. It's better
+ // to reuse the same display list and paint that one twice, one for selection
+ // background, one for generating text mask.
+
+ gfxContext* sourceCtx = aContext->ThebesContext();
+ gfxRect bounds =
+ nsLayoutUtils::RectToGfxRect(aFillRect,
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+
+ {
+ // Paint text selection background into sourceCtx.
+ gfxContextMatrixAutoSaveRestore save(sourceCtx);
+ sourceCtx->SetMatrix(sourceCtx->CurrentMatrix().Translate(bounds.TopLeft()));
+
+ nsLayoutUtils::PaintFrame(aContext, aFrame,
+ nsRect(nsPoint(0, 0), aFrame->GetSize()),
+ NS_RGB(255, 255, 255),
+ nsDisplayListBuilderMode::PAINTING_SELECTION_BACKGROUND);
+ }
+
+ // Evaluate required surface size.
+ IntRect drawRect;
+ {
+ gfxContextMatrixAutoSaveRestore matRestore(sourceCtx);
+
+ sourceCtx->SetMatrix(gfxMatrix());
+ gfxRect clipRect = sourceCtx->GetClipExtents();
+ drawRect = RoundedOut(ToRect(clipRect));
+ }
+
+ // Create a mask surface.
+ RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget();
+ RefPtr<DrawTarget> maskDT =
+ sourceTarget->CreateSimilarDrawTarget(drawRect.Size(),
+ SurfaceFormat::A8);
+ if (!maskDT || !maskDT->IsValid()) {
+ return false;
+ }
+ RefPtr<gfxContext> maskCtx = gfxContext::CreatePreservingTransformOrNull(maskDT);
+ MOZ_ASSERT(maskCtx);
+ gfxMatrix currentMatrix = sourceCtx->CurrentMatrix();
+ maskCtx->SetMatrix(gfxMatrix::Translation(bounds.TopLeft()) *
+ currentMatrix *
+ gfxMatrix::Translation(-drawRect.TopLeft()));
+
+ // Shade text shape into mask A8 surface.
+ nsRenderingContext rc(maskCtx);
+ nsLayoutUtils::PaintFrame(&rc, aFrame,
+ nsRect(nsPoint(0, 0), aFrame->GetSize()),
+ NS_RGB(255, 255, 255),
+ nsDisplayListBuilderMode::GENERATE_GLYPH);
+
+ // Push the generated mask into aContext, so that the caller can pop and
+ // blend with it.
+ Matrix maskTransform = ToMatrix(currentMatrix) *
+ Matrix::Translation(-drawRect.x, -drawRect.y);
+ maskTransform.Invert();
+
+ RefPtr<SourceSurface> maskSurface = maskDT->Snapshot();
+ sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, maskSurface, maskTransform);
+
+ return true;
+}
+
+/* static */ void
+nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ nsIFrame* aFrame,
+ nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "inconsistent property flags");
+
+ // This function can be called in two ways: from
+ // nsDisplay*::BuildLayer while constructing a layer (with all
+ // pointers non-null), or from RestyleManager's handling of
+ // UpdateOpacityLayer/UpdateTransformLayer hints.
+ MOZ_ASSERT(!aBuilder == !aItem,
+ "should only be called in two configurations, with both "
+ "aBuilder and aItem, or with neither");
+ MOZ_ASSERT(!aItem || aFrame == aItem->Frame(), "frame mismatch");
+
+ // Only send animations to a layer that is actually using
+ // off-main-thread compositing.
+ if (aLayer->Manager()->GetBackendType() !=
+ layers::LayersBackend::LAYERS_CLIENT) {
+ return;
+ }
+
+ bool pending = !aBuilder;
+
+ if (pending) {
+ aLayer->ClearAnimationsForNextTransaction();
+ } else {
+ aLayer->ClearAnimations();
+ }
+
+ // Update the animation generation on the layer. We need to do this before
+ // any early returns since even if we don't add any animations to the
+ // layer, we still need to mark it as up-to-date with regards to animations.
+ // Otherwise, in RestyleManager we'll notice the discrepancy between the
+ // animation generation numbers and update the layer indefinitely.
+ uint64_t animationGeneration =
+ RestyleManager::GetAnimationGenerationForFrame(aFrame);
+ aLayer->SetAnimationGeneration(animationGeneration);
+
+ EffectCompositor::ClearIsRunningOnCompositor(aFrame, aProperty);
+ nsTArray<RefPtr<dom::Animation>> compositorAnimations =
+ EffectCompositor::GetAnimationsForCompositor(aFrame, aProperty);
+ if (compositorAnimations.IsEmpty()) {
+ return;
+ }
+
+ // If the frame is not prerendered, bail out.
+ // Do this check only during layer construction; during updating the
+ // caller is required to check it appropriately.
+ if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) {
+ // EffectCompositor needs to know that we refused to run this animation
+ // asynchronously so that it will not throttle the main thread
+ // animation.
+ aFrame->Properties().Set(nsIFrame::RefusedAsyncAnimationProperty(), true);
+
+ // We need to schedule another refresh driver run so that EffectCompositor
+ // gets a chance to unthrottle the animation.
+ aFrame->SchedulePaint();
+ return;
+ }
+
+ AnimationData data;
+ if (aProperty == eCSSProperty_transform) {
+ // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
+ // the dimensions of refBox. That said, we only get here if there are CSS
+ // animations or transitions on this element, and that is likely to be a
+ // lot rarer than transforms on SVG (the frequency of which drives the need
+ // for TransformReferenceBox).
+ TransformReferenceBox refBox(aFrame);
+ nsRect bounds(0, 0, refBox.Width(), refBox.Height());
+ // all data passed directly to the compositor should be in dev pixels
+ int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
+ float scale = devPixelsToAppUnits;
+ Point3D offsetToTransformOrigin =
+ nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, scale, &bounds);
+ nsPoint origin;
+ if (aItem) {
+ // This branch is for display items to leverage the cache of
+ // nsDisplayListBuilder.
+ origin = aItem->ToReferenceFrame();
+ } else {
+ // This branch is running for restyling.
+ // Animations are animated at the coordination of the reference
+ // frame outside, not the given frame itself. The given frame
+ // is also reference frame too, so the parent's reference frame
+ // are used.
+ nsIFrame* referenceFrame =
+ nsLayoutUtils::GetReferenceFrame(nsLayoutUtils::GetCrossDocParentFrame(aFrame));
+ origin = aFrame->GetOffsetToCrossDoc(referenceFrame);
+ }
+
+ data = TransformData(origin, offsetToTransformOrigin,
+ bounds, devPixelsToAppUnits);
+ } else if (aProperty == eCSSProperty_opacity) {
+ data = null_t();
+ }
+
+ AddAnimationsForProperty(aFrame, aProperty, compositorAnimations,
+ aLayer, data, pending);
+}
+
+nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode, bool aBuildCaret)
+ : mReferenceFrame(aReferenceFrame),
+ mIgnoreScrollFrame(nullptr),
+ mLayerEventRegions(nullptr),
+ mCurrentTableItem(nullptr),
+ mCurrentFrame(aReferenceFrame),
+ mCurrentReferenceFrame(aReferenceFrame),
+ mCurrentAGR(&mRootAGR),
+ mRootAGR(aReferenceFrame, nullptr),
+ mUsedAGRBudget(0),
+ mDirtyRect(-1,-1,-1,-1),
+ mGlassDisplayItem(nullptr),
+ mScrollInfoItemsForHoisting(nullptr),
+ mMode(aMode),
+ mCurrentScrollParentId(FrameMetrics::NULL_SCROLL_ID),
+ mCurrentScrollbarTarget(FrameMetrics::NULL_SCROLL_ID),
+ mCurrentScrollbarFlags(0),
+ mPerspectiveItemIndex(0),
+ mSVGEffectsBuildingDepth(0),
+ mContainsBlendMode(false),
+ mIsBuildingScrollbar(false),
+ mCurrentScrollbarWillHaveLayer(false),
+ mBuildCaret(aBuildCaret),
+ mIgnoreSuppression(false),
+ mIsAtRootOfPseudoStackingContext(false),
+ mIncludeAllOutOfFlows(false),
+ mDescendIntoSubdocuments(true),
+ mSelectedFramesOnly(false),
+ mAccurateVisibleRegions(false),
+ mAllowMergingAndFlattening(true),
+ mWillComputePluginGeometry(false),
+ mInTransform(false),
+ mIsInChromePresContext(false),
+ mSyncDecodeImages(false),
+ mIsPaintingToWindow(false),
+ mIsCompositingCheap(false),
+ mContainsPluginItem(false),
+ mAncestorHasApzAwareEventHandler(false),
+ mHaveScrollableDisplayPort(false),
+ mWindowDraggingAllowed(false),
+ mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
+ mForceLayerForScrollParent(false),
+ mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
+ mBuildingInvisibleItems(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayListBuilder);
+ PL_InitArenaPool(&mPool, "displayListArena", 4096,
+ std::max(NS_ALIGNMENT_OF(void*),NS_ALIGNMENT_OF(double))-1);
+
+ nsPresContext* pc = aReferenceFrame->PresContext();
+ nsIPresShell *shell = pc->PresShell();
+ if (pc->IsRenderingOnlySelection()) {
+ nsCOMPtr<nsISelectionController> selcon(do_QueryInterface(shell));
+ if (selcon) {
+ selcon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(mBoundingSelection));
+ }
+ }
+
+ mFrameToAnimatedGeometryRootMap.Put(aReferenceFrame, &mRootAGR);
+
+ nsCSSRendering::BeginFrameTreesLocked();
+ static_assert(nsDisplayItem::TYPE_MAX < (1 << nsDisplayItem::TYPE_BITS),
+ "Check nsDisplayItem::TYPE_MAX should not overflow");
+}
+
+static void MarkFrameForDisplay(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
+ if (f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)
+ return;
+ f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
+ if (f == aStopAtFrame) {
+ // we've reached a frame that we know will be painted, so we can stop.
+ break;
+ }
+ }
+}
+
+bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem)
+{
+ return aItem == mGlassDisplayItem || aItem->ClearsBackground();
+}
+
+AnimatedGeometryRoot*
+nsDisplayListBuilder::WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot* aParent /* = nullptr */)
+{
+ MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot));
+
+ AnimatedGeometryRoot* result = nullptr;
+ if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) {
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aAnimatedGeometryRoot));
+ AnimatedGeometryRoot* parent = aParent;
+ if (!parent) {
+ nsIFrame* parentFrame = nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot);
+ if (parentFrame) {
+ nsIFrame* parentAGRFrame = FindAnimatedGeometryRootFrameFor(parentFrame);
+ parent = WrapAGRForFrame(parentAGRFrame);
+ }
+ }
+ result = new (this) AnimatedGeometryRoot(aAnimatedGeometryRoot, parent);
+ mFrameToAnimatedGeometryRootMap.Put(aAnimatedGeometryRoot, result);
+ }
+ MOZ_ASSERT(!aParent || result->mParentAGR == aParent);
+ return result;
+}
+
+AnimatedGeometryRoot*
+nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame)
+{
+ if (!IsPaintingToWindow()) {
+ return &mRootAGR;
+ }
+ if (aFrame == mCurrentFrame) {
+ return mCurrentAGR;
+ }
+ AnimatedGeometryRoot* result = nullptr;
+ if (mFrameToAnimatedGeometryRootMap.Get(aFrame, &result)) {
+ return result;
+ }
+
+ nsIFrame* agrFrame = FindAnimatedGeometryRootFrameFor(aFrame);
+ result = WrapAGRForFrame(agrFrame);
+ mFrameToAnimatedGeometryRootMap.Put(aFrame, result);
+ return result;
+}
+
+AnimatedGeometryRoot*
+nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsDisplayItem* aItem)
+{
+ if (aItem->ShouldFixToViewport(this)) {
+ // Make its active scrolled root be the active scrolled root of
+ // the enclosing viewport, since it shouldn't be scrolled by scrolled
+ // frames in its document. InvalidateFixedBackgroundFramesFromList in
+ // nsGfxScrollFrame will not repaint this item when scrolling occurs.
+ nsIFrame* viewportFrame =
+ nsLayoutUtils::GetClosestFrameOfType(aItem->Frame(), nsGkAtoms::viewportFrame, RootReferenceFrame());
+ if (viewportFrame) {
+ return FindAnimatedGeometryRootFor(viewportFrame);
+ }
+ }
+ return FindAnimatedGeometryRootFor(aItem->Frame());
+}
+
+
+void nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame,
+ nsIFrame* aFrame,
+ const nsRect& aDirtyRect)
+{
+ nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect;
+ if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(aFrame) &&
+ IsPaintingToWindow()) {
+ NS_ASSERTION(aDirtyFrame == aFrame->GetParent(), "Dirty frame should be viewport frame");
+ // position: fixed items are reflowed into and only drawn inside the
+ // viewport, or the scroll position clamping scrollport size, if one is
+ // set.
+ nsIPresShell* ps = aFrame->PresContext()->PresShell();
+ dirtyRectRelativeToDirtyFrame.MoveTo(0, 0);
+ if (ps->IsScrollPositionClampingScrollPortSizeSet()) {
+ dirtyRectRelativeToDirtyFrame.SizeTo(ps->GetScrollPositionClampingScrollPortSize());
+ } else {
+ dirtyRectRelativeToDirtyFrame.SizeTo(aDirtyFrame->GetSize());
+ }
+ }
+ nsRect dirty = dirtyRectRelativeToDirtyFrame - aFrame->GetOffsetTo(aDirtyFrame);
+ nsRect overflowRect = aFrame->GetVisualOverflowRect();
+
+ if (aFrame->IsTransformed() &&
+ EffectCompositor::HasAnimationsForCompositor(aFrame,
+ eCSSProperty_transform)) {
+ /**
+ * Add a fuzz factor to the overflow rectangle so that elements only just
+ * out of view are pulled into the display list, so they can be
+ * prerendered if necessary.
+ */
+ overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32));
+ }
+
+ if (!dirty.IntersectRect(dirty, overflowRect) &&
+ !(aFrame->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ return;
+ }
+
+ const DisplayItemClip* oldClip = mClipState.GetClipForContainingBlockDescendants();
+ const DisplayItemScrollClip* sc = mClipState.GetCurrentInnermostScrollClip();
+ OutOfFlowDisplayData* data = new OutOfFlowDisplayData(oldClip, sc, dirty);
+ aFrame->Properties().Set(nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data);
+
+ MarkFrameForDisplay(aFrame, aDirtyFrame);
+}
+
+static void UnmarkFrameForDisplay(nsIFrame* aFrame) {
+ nsPresContext* presContext = aFrame->PresContext();
+ presContext->PropertyTable()->
+ Delete(aFrame, nsDisplayListBuilder::OutOfFlowDisplayDataProperty());
+
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
+ if (!(f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO))
+ return;
+ f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
+ }
+}
+
+nsDisplayListBuilder::~nsDisplayListBuilder() {
+ NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
+ "All frames should have been unmarked");
+ NS_ASSERTION(mPresShellStates.Length() == 0,
+ "All presshells should have been exited");
+ NS_ASSERTION(!mCurrentTableItem, "No table item should be active");
+
+ nsCSSRendering::EndFrameTreesLocked();
+
+ for (DisplayItemClip* c : mDisplayItemClipsToDestroy) {
+ c->DisplayItemClip::~DisplayItemClip();
+ }
+ for (DisplayItemScrollClip* c : mScrollClipsToDestroy) {
+ c->DisplayItemScrollClip::~DisplayItemScrollClip();
+ }
+
+ PL_FinishArenaPool(&mPool);
+ MOZ_COUNT_DTOR(nsDisplayListBuilder);
+}
+
+uint32_t
+nsDisplayListBuilder::GetBackgroundPaintFlags() {
+ uint32_t flags = 0;
+ if (mSyncDecodeImages) {
+ flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES;
+ }
+ if (mIsPaintingToWindow) {
+ flags |= nsCSSRendering::PAINTBG_TO_WINDOW;
+ }
+ return flags;
+}
+
+void
+nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
+ const nsRegion& aRegion)
+{
+ if (aRegion.IsEmpty())
+ return;
+
+ nsRegion tmp;
+ tmp.Sub(*aVisibleRegion, aRegion);
+ // Don't let *aVisibleRegion get too complex, but don't let it fluff out
+ // to its bounds either, which can be very bad (see bug 516740).
+ // Do let aVisibleRegion get more complex if by doing so we reduce its
+ // area by at least half.
+ if (GetAccurateVisibleRegions() || tmp.GetNumRects() <= 15 ||
+ tmp.Area() <= aVisibleRegion->Area()/2) {
+ *aVisibleRegion = tmp;
+ }
+}
+
+nsCaret *
+nsDisplayListBuilder::GetCaret() {
+ RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret();
+ return caret;
+}
+
+void
+nsDisplayListBuilder::EnterPresShell(nsIFrame* aReferenceFrame,
+ bool aPointerEventsNoneDoc)
+{
+ PresShellState* state = mPresShellStates.AppendElement();
+ state->mPresShell = aReferenceFrame->PresContext()->PresShell();
+ state->mCaretFrame = nullptr;
+ state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length();
+
+ state->mPresShell->UpdateCanvasBackground();
+
+ if (mIsPaintingToWindow) {
+ mReferenceFrame->AddPaintedPresShell(state->mPresShell);
+
+ state->mPresShell->IncrementPaintCount();
+ }
+
+ bool buildCaret = mBuildCaret;
+ if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) {
+ state->mIsBackgroundOnly = false;
+ } else {
+ state->mIsBackgroundOnly = true;
+ buildCaret = false;
+ }
+
+ bool pointerEventsNone = aPointerEventsNoneDoc;
+ if (IsInSubdocument()) {
+ pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2].mInsidePointerEventsNoneDoc;
+ }
+ state->mInsidePointerEventsNoneDoc = pointerEventsNone;
+
+ if (!buildCaret)
+ return;
+
+ RefPtr<nsCaret> caret = state->mPresShell->GetCaret();
+ state->mCaretFrame = caret->GetPaintGeometry(&state->mCaretRect);
+ if (state->mCaretFrame) {
+ mFramesMarkedForDisplay.AppendElement(state->mCaretFrame);
+ MarkFrameForDisplay(state->mCaretFrame, nullptr);
+ }
+
+ nsPresContext* pc = aReferenceFrame->PresContext();
+ nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell();
+ if (docShell) {
+ docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
+ }
+ mIsInChromePresContext = pc->IsChrome();
+}
+
+// A non-blank paint is a paint that does not just contain the canvas background.
+static bool
+DisplayListIsNonBlank(nsDisplayList* aList)
+{
+ for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
+ switch (i->GetType()) {
+ case nsDisplayItem::TYPE_LAYER_EVENT_REGIONS:
+ case nsDisplayItem::TYPE_CANVAS_BACKGROUND_COLOR:
+ case nsDisplayItem::TYPE_CANVAS_BACKGROUND_IMAGE:
+ continue;
+ case nsDisplayItem::TYPE_SOLID_COLOR:
+ case nsDisplayItem::TYPE_BACKGROUND:
+ case nsDisplayItem::TYPE_BACKGROUND_COLOR:
+ if (i->Frame()->GetType() == nsGkAtoms::canvasFrame) {
+ continue;
+ }
+ return true;
+ default:
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsDisplayListBuilder::LeavePresShell(nsIFrame* aReferenceFrame, nsDisplayList* aPaintedContents)
+{
+ NS_ASSERTION(CurrentPresShellState()->mPresShell ==
+ aReferenceFrame->PresContext()->PresShell(),
+ "Presshell mismatch");
+
+ if (mIsPaintingToWindow) {
+ nsPresContext* pc = aReferenceFrame->PresContext();
+ if (!pc->HadNonBlankPaint()) {
+ if (!CurrentPresShellState()->mIsBackgroundOnly &&
+ DisplayListIsNonBlank(aPaintedContents)) {
+ pc->NotifyNonBlankPaint();
+ }
+ }
+ }
+
+ ResetMarkedFramesForDisplayList();
+ mPresShellStates.SetLength(mPresShellStates.Length() - 1);
+
+ if (!mPresShellStates.IsEmpty()) {
+ nsPresContext* pc = CurrentPresContext();
+ nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell();
+ if (docShell) {
+ docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
+ }
+ mIsInChromePresContext = pc->IsChrome();
+ }
+}
+
+void
+nsDisplayListBuilder::ResetMarkedFramesForDisplayList()
+{
+ // Unmark and pop off the frames marked for display in this pres shell.
+ uint32_t firstFrameForShell = CurrentPresShellState()->mFirstFrameMarkedForDisplay;
+ for (uint32_t i = firstFrameForShell;
+ i < mFramesMarkedForDisplay.Length(); ++i) {
+ UnmarkFrameForDisplay(mFramesMarkedForDisplay[i]);
+ }
+ mFramesMarkedForDisplay.SetLength(firstFrameForShell);
+}
+
+void
+nsDisplayListBuilder::MarkFramesForDisplayList(nsIFrame* aDirtyFrame,
+ const nsFrameList& aFrames,
+ const nsRect& aDirtyRect) {
+ mFramesMarkedForDisplay.SetCapacity(mFramesMarkedForDisplay.Length() + aFrames.GetLength());
+ for (nsIFrame* e : aFrames) {
+ // Skip the AccessibleCaret frame when building no caret.
+ if (!IsBuildingCaret()) {
+ nsIContent* content = e->GetContent();
+ if (content && content->IsInNativeAnonymousSubtree() && content->IsElement()) {
+ auto classList = content->AsElement()->ClassList();
+ if (classList->Contains(NS_LITERAL_STRING("moz-accessiblecaret"))) {
+ continue;
+ }
+ }
+ }
+ mFramesMarkedForDisplay.AppendElement(e);
+ MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, aDirtyRect);
+ }
+}
+
+/**
+ * Mark all preserve-3d children with
+ * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure
+ * nsFrame::BuildDisplayListForChild() would visit them. Also compute
+ * dirty rect for preserve-3d children.
+ *
+ * @param aDirtyFrame is the frame to mark children extending context.
+ */
+void
+nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList(nsIFrame* aDirtyFrame)
+{
+ AutoTArray<nsIFrame::ChildList,4> childListArray;
+ aDirtyFrame->GetChildLists(&childListArray);
+ nsIFrame::ChildListArrayIterator lists(childListArray);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame *child = childFrames.get();
+ if (child->Combines3DTransformWithAncestors()) {
+ mFramesMarkedForDisplay.AppendElement(child);
+ MarkFrameForDisplay(child, aDirtyFrame);
+ }
+ }
+ }
+}
+
+void*
+nsDisplayListBuilder::Allocate(size_t aSize)
+{
+ void *tmp;
+ PL_ARENA_ALLOCATE(tmp, &mPool, aSize);
+ if (!tmp) {
+ NS_ABORT_OOM(aSize);
+ }
+ return tmp;
+}
+
+const DisplayItemClip*
+nsDisplayListBuilder::AllocateDisplayItemClip(const DisplayItemClip& aOriginal)
+{
+ void* p = Allocate(sizeof(DisplayItemClip));
+ if (!aOriginal.GetRoundedRectCount()) {
+ memcpy(p, &aOriginal, sizeof(DisplayItemClip));
+ return static_cast<DisplayItemClip*>(p);
+ }
+
+ DisplayItemClip* c = new (KnownNotNull, p) DisplayItemClip(aOriginal);
+ mDisplayItemClipsToDestroy.AppendElement(c);
+ return c;
+}
+
+DisplayItemScrollClip*
+nsDisplayListBuilder::AllocateDisplayItemScrollClip(const DisplayItemScrollClip* aParent,
+ nsIScrollableFrame* aScrollableFrame,
+ const DisplayItemClip* aClip,
+ bool aIsAsyncScrollable)
+{
+ void* p = Allocate(sizeof(DisplayItemScrollClip));
+ DisplayItemScrollClip* c =
+ new (KnownNotNull, p) DisplayItemScrollClip(aParent, aScrollableFrame,
+ aClip, aIsAsyncScrollable);
+ mScrollClipsToDestroy.AppendElement(c);
+ return c;
+}
+
+const nsIFrame*
+nsDisplayListBuilder::FindReferenceFrameFor(const nsIFrame *aFrame,
+ nsPoint* aOffset)
+{
+ if (aFrame == mCurrentFrame) {
+ if (aOffset) {
+ *aOffset = mCurrentOffsetToReferenceFrame;
+ }
+ return mCurrentReferenceFrame;
+ }
+ for (const nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f))
+ {
+ if (f == mReferenceFrame || f->IsTransformed()) {
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(f);
+ }
+ return f;
+ }
+ }
+ if (aOffset) {
+ *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ }
+ return mReferenceFrame;
+}
+
+// Sticky frames are active if their nearest scrollable frame is also active.
+static bool
+IsStickyFrameActive(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aParent)
+{
+ MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY);
+
+ // Find the nearest scrollframe.
+ nsIFrame* cursor = aFrame;
+ nsIFrame* parent = aParent;
+ if (!parent) {
+ parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+ }
+ while (parent->GetType() != nsGkAtoms::scrollFrame) {
+ cursor = parent;
+ if ((parent = nsLayoutUtils::GetCrossDocParentFrame(cursor)) == nullptr) {
+ return false;
+ }
+ }
+
+ nsIScrollableFrame* sf = do_QueryFrame(parent);
+ return sf->IsScrollingActive(aBuilder) && sf->GetScrolledFrame() == cursor;
+}
+
+bool
+nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent)
+{
+ if (aFrame == mReferenceFrame) {
+ return true;
+ }
+ if (!IsPaintingToWindow()) {
+ if (aParent) {
+ *aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+ }
+ return false;
+ }
+
+ if (nsLayoutUtils::IsPopup(aFrame))
+ return true;
+ if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame)) {
+ const bool inBudget = AddToAGRBudget(aFrame);
+ if (inBudget) {
+ return true;
+ }
+ }
+ if (!aFrame->GetParent() &&
+ nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) {
+ // Viewport frames in a display port need to be animated geometry roots
+ // for background-attachment:fixed elements.
+ return true;
+ }
+ if (aFrame->IsTransformed()) {
+ return true;
+ }
+
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+ if (!parent)
+ return true;
+
+ nsIAtom* parentType = parent->GetType();
+ // Treat the slider thumb as being as an active scrolled root when it wants
+ // its own layer so that it can move without repainting.
+ if (parentType == nsGkAtoms::sliderFrame && nsLayoutUtils::IsScrollbarThumbLayerized(aFrame)) {
+ return true;
+ }
+
+ if (aFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY &&
+ IsStickyFrameActive(this, aFrame, parent))
+ {
+ return true;
+ }
+
+ if (parentType == nsGkAtoms::scrollFrame || parentType == nsGkAtoms::listControlFrame) {
+ nsIScrollableFrame* sf = do_QueryFrame(parent);
+ if (sf->IsScrollingActive(this) && sf->GetScrolledFrame() == aFrame) {
+ return true;
+ }
+ }
+
+ // Fixed-pos frames are parented by the viewport frame, which has no parent.
+ if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(aFrame)) {
+ return true;
+ }
+
+ if (aParent) {
+ *aParent = parent;
+ }
+ return false;
+}
+
+nsIFrame*
+nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aFrame));
+ nsIFrame* cursor = aFrame;
+ while (cursor != RootReferenceFrame()) {
+ nsIFrame* next;
+ if (IsAnimatedGeometryRoot(cursor, &next))
+ return cursor;
+ cursor = next;
+ }
+ return cursor;
+}
+
+void
+nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot()
+{
+ if (*mCurrentAGR != mCurrentFrame &&
+ IsAnimatedGeometryRoot(const_cast<nsIFrame*>(mCurrentFrame))) {
+ AnimatedGeometryRoot* oldAGR = mCurrentAGR;
+ mCurrentAGR = WrapAGRForFrame(const_cast<nsIFrame*>(mCurrentFrame), mCurrentAGR);
+
+ // Iterate the AGR cache and look for any objects that reference the old AGR and check
+ // to see if they need to be updated. AGRs can be in the cache multiple times, so we may
+ // end up doing the work multiple times for AGRs that don't change.
+ for (auto iter = mFrameToAnimatedGeometryRootMap.Iter(); !iter.Done(); iter.Next()) {
+ AnimatedGeometryRoot* cached = iter.UserData();
+ if (cached->mParentAGR == oldAGR && cached != mCurrentAGR) {
+ // It's possible that this cached AGR struct that has the old AGR as a parent
+ // should instead have mCurrentFrame has a parent.
+ nsIFrame* parent = FindAnimatedGeometryRootFrameFor(*cached);
+ MOZ_ASSERT(parent == mCurrentFrame || parent == *oldAGR);
+ if (parent == mCurrentFrame) {
+ cached->mParentAGR = mCurrentAGR;
+ }
+ }
+ }
+ }
+}
+
+void
+nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame)
+{
+ if (!mWindowDraggingAllowed || !IsForPainting()) {
+ return;
+ }
+
+ const nsStyleUIReset* styleUI = aFrame->StyleUIReset();
+ if (styleUI->mWindowDragging == StyleWindowDragging::Default) {
+ // This frame has the default value and doesn't influence the window
+ // dragging region.
+ return;
+ }
+
+ LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame;
+
+ // The const_cast is for nsLayoutUtils::GetTransformToAncestor.
+ nsIFrame* referenceFrame = const_cast<nsIFrame*>(FindReferenceFrameFor(aFrame));
+
+ if (IsInTransform()) {
+ // Only support 2d rectilinear transforms. Transform support is needed for
+ // the horizontal flip transform that's applied to the urlbar textbox in
+ // RTL mode - it should be able to exclude itself from the draggable region.
+ referenceFrameToRootReferenceFrame =
+ ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>(
+ nsLayoutUtils::GetTransformToAncestor(referenceFrame, mReferenceFrame));
+ Matrix referenceFrameToRootReferenceFrame2d;
+ if (!referenceFrameToRootReferenceFrame.Is2D(&referenceFrameToRootReferenceFrame2d) ||
+ !referenceFrameToRootReferenceFrame2d.IsRectilinear()) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(referenceFrame == mReferenceFrame,
+ "referenceFrameToRootReferenceFrame needs to be adjusted");
+ }
+
+ // We do some basic visibility checking on the frame's border box here.
+ // We intersect it both with the current dirty rect and with the current
+ // clip. Either one is just a conservative approximation on its own, but
+ // their intersection luckily works well enough for our purposes, so that
+ // we don't have to do full-blown visibility computations.
+ // The most important case we need to handle is the scrolled-off tab:
+ // If the tab bar overflows, tab parts that are clipped by the scrollbox
+ // should not be allowed to interfere with the window dragging region. Using
+ // just the current DisplayItemClip is not enough to cover this case
+ // completely because clips are reset while building stacking context
+ // contents, so for example we'd fail to clip frames that have a clip path
+ // applied to them. But the current dirty rect doesn't get reset in that
+ // case, so we use it to make this case work.
+ nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mDirtyRect);
+ borderBox += ToReferenceFrame(aFrame);
+ const DisplayItemClip* clip = ClipState().GetCurrentCombinedClip(this);
+ if (clip) {
+ borderBox = clip->ApplyNonRoundedIntersection(borderBox);
+ }
+ if (!borderBox.IsEmpty()) {
+ LayoutDeviceRect devPixelBorderBox =
+ LayoutDevicePixel::FromAppUnits(borderBox, aFrame->PresContext()->AppUnitsPerDevPixel());
+ LayoutDeviceRect transformedDevPixelBorderBox =
+ TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox);
+ transformedDevPixelBorderBox.Round();
+ LayoutDeviceIntRect transformedDevPixelBorderBoxInt;
+ if (transformedDevPixelBorderBox.ToIntRect(&transformedDevPixelBorderBoxInt)) {
+ if (styleUI->mWindowDragging == StyleWindowDragging::Drag) {
+ mWindowDraggingRegion.OrWith(transformedDevPixelBorderBoxInt);
+ } else {
+ mWindowNoDraggingRegion.OrWith(transformedDevPixelBorderBoxInt);
+ }
+ }
+ }
+}
+
+LayoutDeviceIntRegion
+nsDisplayListBuilder::GetWindowDraggingRegion() const
+{
+ LayoutDeviceIntRegion result;
+ result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion);;
+ return result;
+}
+
+const uint32_t gWillChangeAreaMultiplier = 3;
+static uint32_t GetLayerizationCost(const nsSize& aSize) {
+ // There's significant overhead for each layer created from Gecko
+ // (IPC+Shared Objects) and from the backend (like an OpenGL texture).
+ // Therefore we set a minimum cost threshold of a 64x64 area.
+ int minBudgetCost = 64 * 64;
+
+ uint32_t budgetCost =
+ std::max(minBudgetCost,
+ nsPresContext::AppUnitsToIntCSSPixels(aSize.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(aSize.height));
+
+ return budgetCost;
+}
+
+bool
+nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame,
+ const nsSize& aSize) {
+ if (mWillChangeBudgetSet.Contains(aFrame)) {
+ return true; // Already accounted
+ }
+
+ nsPresContext* key = aFrame->PresContext();
+ if (!mWillChangeBudget.Contains(key)) {
+ mWillChangeBudget.Put(key, DocumentWillChangeBudget());
+ }
+
+ DocumentWillChangeBudget budget;
+ mWillChangeBudget.Get(key, &budget);
+
+ nsRect area = aFrame->PresContext()->GetVisibleArea();
+ uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+ uint32_t cost = GetLayerizationCost(aSize);
+ bool onBudget = (budget.mBudget + cost) /
+ gWillChangeAreaMultiplier < budgetLimit;
+
+ if (onBudget) {
+ budget.mBudget += cost;
+ mWillChangeBudget.Put(key, budget);
+ mWillChangeBudgetSet.PutEntry(aFrame);
+ }
+
+ return onBudget;
+}
+
+bool
+nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame,
+ const nsSize& aSize) {
+ bool onBudget = AddToWillChangeBudget(aFrame, aSize);
+
+ if (!onBudget) {
+ nsString usageStr;
+ usageStr.AppendInt(GetLayerizationCost(aSize));
+
+ nsString multiplierStr;
+ multiplierStr.AppendInt(gWillChangeAreaMultiplier);
+
+ nsString limitStr;
+ nsRect area = aFrame->PresContext()->GetVisibleArea();
+ uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+ limitStr.AppendInt(budgetLimit);
+
+ const char16_t* params[] = { multiplierStr.get(), limitStr.get() };
+ aFrame->PresContext()->Document()->WarnOnceAbout(
+ nsIDocument::eIgnoringWillChangeOverBudget, false,
+ params, ArrayLength(params));
+ }
+ return onBudget;
+}
+
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+const float gAGRBudgetAreaMultiplier = 0.3;
+#else
+const float gAGRBudgetAreaMultiplier = 3.0;
+#endif
+
+bool
+nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame)
+{
+ if (mAGRBudgetSet.Contains(aFrame)) {
+ return true;
+ }
+
+ const nsPresContext* presContext = aFrame->PresContext()->GetRootPresContext();
+ if (!presContext) {
+ return false;
+ }
+
+ const nsRect area = presContext->GetVisibleArea();
+ const uint32_t budgetLimit = gAGRBudgetAreaMultiplier *
+ nsPresContext::AppUnitsToIntCSSPixels(area.width) *
+ nsPresContext::AppUnitsToIntCSSPixels(area.height);
+
+ const uint32_t cost = GetLayerizationCost(aFrame->GetSize());
+ const bool onBudget = mUsedAGRBudget + cost < budgetLimit;
+
+ if (onBudget) {
+ mUsedAGRBudget += cost;
+ mAGRBudgetSet.PutEntry(aFrame);
+ }
+
+ return onBudget;
+}
+
+void
+nsDisplayListBuilder::EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage)
+{
+ MOZ_ASSERT(mSVGEffectsBuildingDepth >= 0);
+ MOZ_ASSERT(aHoistedItemsStorage);
+ if (mSVGEffectsBuildingDepth == 0) {
+ MOZ_ASSERT(!mScrollInfoItemsForHoisting);
+ mScrollInfoItemsForHoisting = aHoistedItemsStorage;
+ }
+ mSVGEffectsBuildingDepth++;
+}
+
+void
+nsDisplayListBuilder::ExitSVGEffectsContents()
+{
+ mSVGEffectsBuildingDepth--;
+ MOZ_ASSERT(mSVGEffectsBuildingDepth >= 0);
+ MOZ_ASSERT(mScrollInfoItemsForHoisting);
+ if (mSVGEffectsBuildingDepth == 0) {
+ mScrollInfoItemsForHoisting = nullptr;
+ }
+}
+
+void
+nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLayer* aScrollInfoItem)
+{
+ MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting());
+ MOZ_ASSERT(mScrollInfoItemsForHoisting);
+ mScrollInfoItemsForHoisting->AppendNewToTop(aScrollInfoItem);
+}
+
+bool
+nsDisplayListBuilder::IsBuildingLayerEventRegions()
+{
+ if (IsPaintingToWindow()) {
+ // Note: this function and LayerEventRegionsEnabled are the only places
+ // that get to query LayoutEventRegionsEnabled 'directly' - other code
+ // should call this function.
+ return gfxPrefs::LayoutEventRegionsEnabledDoNotUseDirectly() ||
+ mAsyncPanZoomEnabled;
+ }
+ return false;
+}
+
+/* static */ bool
+nsDisplayListBuilder::LayerEventRegionsEnabled()
+{
+ // Note: this function and IsBuildingLayerEventRegions are the only places
+ // that get to query LayoutEventRegionsEnabled 'directly' - other code
+ // should call this function.
+ return gfxPrefs::LayoutEventRegionsEnabledDoNotUseDirectly() ||
+ gfxPlatform::AsyncPanZoomEnabled();
+}
+
+void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const
+{
+ aDestination.BorderBackground()->AppendToTop(BorderBackground());
+ aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds());
+ aDestination.Floats()->AppendToTop(Floats());
+ aDestination.Content()->AppendToTop(Content());
+ aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants());
+ aDestination.Outlines()->AppendToTop(Outlines());
+}
+
+static void
+MoveListTo(nsDisplayList* aList, nsTArray<nsDisplayItem*>* aElements) {
+ nsDisplayItem* item;
+ while ((item = aList->RemoveBottom()) != nullptr) {
+ aElements->AppendElement(item);
+ }
+}
+
+nsRect
+nsDisplayList::GetBounds(nsDisplayListBuilder* aBuilder) const {
+ nsRect bounds;
+ for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
+ bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder));
+ }
+ return bounds;
+}
+
+nsRect
+nsDisplayList::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
+ const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const {
+ nsRect bounds;
+ for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
+ nsRect r = i->GetClippedBounds(aBuilder);
+ if (r.IsEmpty()) {
+ continue;
+ }
+ for (auto* sc = i->ScrollClip(); sc && sc != aIncludeScrollClipsUpTo; sc = sc->mParent) {
+ if (sc->mClip && sc->mClip->HasClip()) {
+ if (sc->mIsAsyncScrollable) {
+ // Assume the item can move anywhere in the scroll clip's clip rect.
+ r = sc->mClip->GetClipRect();
+ } else {
+ r = sc->mClip->ApplyNonRoundedIntersection(r);
+ }
+ }
+ }
+ bounds.UnionRect(bounds, r);
+ }
+ return bounds;
+}
+
+nsRect
+nsDisplayList::GetVisibleRect() const {
+ nsRect result;
+ for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
+ result.UnionRect(result, i->GetVisibleRect());
+ }
+ return result;
+}
+
+bool
+nsDisplayList::ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ PROFILER_LABEL("nsDisplayList", "ComputeVisibilityForRoot",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ nsRegion r;
+ r.And(*aVisibleRegion, GetBounds(aBuilder));
+ return ComputeVisibilityForSublist(aBuilder, aVisibleRegion, r.GetBounds());
+}
+
+static nsRegion
+TreatAsOpaque(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+{
+ bool snap;
+ nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap);
+ if (aBuilder->IsForPluginGeometry() &&
+ aItem->GetType() != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS)
+ {
+ // Treat all leaf chrome items as opaque, unless their frames are opacity:0.
+ // Since opacity:0 frames generate an nsDisplayOpacity, that item will
+ // not be treated as opaque here, so opacity:0 chrome content will be
+ // effectively ignored, as it should be.
+ // We treat leaf chrome items as opaque to ensure that they cover
+ // content plugins, for security reasons.
+ // Non-leaf chrome items don't render contents of their own so shouldn't
+ // be treated as opaque (and their bounds is just the union of their
+ // children, which might be a large area their contents don't really cover).
+ nsIFrame* f = aItem->Frame();
+ if (f->PresContext()->IsChrome() && !aItem->GetChildren() &&
+ f->StyleEffects()->mOpacity != 0.0) {
+ opaque = aItem->GetBounds(aBuilder, &snap);
+ }
+ }
+ if (opaque.IsEmpty()) {
+ return opaque;
+ }
+ nsRegion opaqueClipped;
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ opaqueClipped.Or(opaqueClipped,
+ aItem->GetClip().ApproximateIntersectInward(iter.Get()));
+ }
+ return opaqueClipped;
+}
+
+bool
+nsDisplayList::ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion,
+ const nsRect& aListVisibleBounds)
+{
+#ifdef DEBUG
+ nsRegion r;
+ r.And(*aVisibleRegion, GetBounds(aBuilder));
+ NS_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds),
+ "bad aListVisibleBounds");
+#endif
+
+ bool anyVisible = false;
+
+ AutoTArray<nsDisplayItem*, 512> elements;
+ MoveListTo(this, &elements);
+
+ for (int32_t i = elements.Length() - 1; i >= 0; --i) {
+ nsDisplayItem* item = elements[i];
+
+ if (item->mForceNotVisible && !item->GetSameCoordinateSystemChildren()) {
+ NS_ASSERTION(item->mVisibleRect.IsEmpty(),
+ "invisible items should have empty vis rect");
+ } else {
+ nsRect bounds = item->GetClippedBounds(aBuilder);
+
+ nsRegion itemVisible;
+ itemVisible.And(*aVisibleRegion, bounds);
+ item->mVisibleRect = itemVisible.GetBounds();
+ }
+
+ if (item->ComputeVisibility(aBuilder, aVisibleRegion)) {
+ anyVisible = true;
+
+ nsRegion opaque = TreatAsOpaque(item, aBuilder);
+ // Subtract opaque item from the visible region
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque);
+ }
+ AppendToBottom(item);
+ }
+
+ mIsOpaque = !aVisibleRegion->Intersects(aListVisibleBounds);
+ return anyVisible;
+}
+
+static bool
+TriggerPendingAnimationsOnSubDocuments(nsIDocument* aDocument, void* aReadyTime)
+{
+ PendingAnimationTracker* tracker = aDocument->GetPendingAnimationTracker();
+ if (tracker) {
+ nsIPresShell* shell = aDocument->GetShell();
+ // If paint-suppression is in effect then we haven't finished painting
+ // this document yet so we shouldn't start animations
+ if (!shell || !shell->IsPaintingSuppressed()) {
+ const TimeStamp& readyTime = *static_cast<TimeStamp*>(aReadyTime);
+ tracker->TriggerPendingAnimationsOnNextTick(readyTime);
+ }
+ }
+ aDocument->EnumerateSubDocuments(TriggerPendingAnimationsOnSubDocuments,
+ aReadyTime);
+ return true;
+}
+
+static void
+TriggerPendingAnimations(nsIDocument* aDocument,
+ const TimeStamp& aReadyTime) {
+ MOZ_ASSERT(!aReadyTime.IsNull(),
+ "Animation ready time is not set. Perhaps we're using a layer"
+ " manager that doesn't update it");
+ TriggerPendingAnimationsOnSubDocuments(aDocument,
+ const_cast<TimeStamp*>(&aReadyTime));
+}
+
+LayerManager*
+nsDisplayListBuilder::GetWidgetLayerManager(nsView** aView)
+{
+ nsView* view = RootReferenceFrame()->GetView();
+ if (aView) {
+ *aView = view;
+ }
+ if (RootReferenceFrame() != nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) {
+ return nullptr;
+ }
+ nsIWidget* window = RootReferenceFrame()->GetNearestWidget();
+ if (window) {
+ return window->GetLayerManager();
+ }
+ return nullptr;
+}
+
+/**
+ * We paint by executing a layer manager transaction, constructing a
+ * single layer representing the display list, and then making it the
+ * root of the layer manager, drawing into the PaintedLayers.
+ */
+already_AddRefed<LayerManager> nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ uint32_t aFlags) {
+ PROFILER_LABEL("nsDisplayList", "PaintRoot",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ RefPtr<LayerManager> layerManager;
+ bool widgetTransaction = false;
+ bool doBeginTransaction = true;
+ nsView *view = nullptr;
+ if (aFlags & PAINT_USE_WIDGET_LAYERS) {
+ layerManager = aBuilder->GetWidgetLayerManager(&view);
+ if (layerManager) {
+ doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION);
+ widgetTransaction = true;
+ }
+ }
+ if (!layerManager) {
+ if (!aCtx) {
+ NS_WARNING("Nowhere to paint into");
+ return nullptr;
+ }
+ layerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN);
+ }
+
+ // Store the existing layer builder to reinstate it on return.
+ FrameLayerBuilder *oldBuilder = layerManager->GetLayerBuilder();
+
+ FrameLayerBuilder *layerBuilder = new FrameLayerBuilder();
+ layerBuilder->Init(aBuilder, layerManager);
+
+ if (aFlags & PAINT_COMPRESSED) {
+ layerBuilder->SetLayerTreeCompressionMode();
+ }
+
+ if (doBeginTransaction) {
+ if (aCtx) {
+ if (!layerManager->BeginTransactionWithTarget(aCtx->ThebesContext())) {
+ return nullptr;
+ }
+ } else {
+ if (!layerManager->BeginTransaction()) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+ FrameLayerBuilder::InvalidateAllLayers(layerManager);
+ }
+
+ if (widgetTransaction) {
+ layerBuilder->DidBeginRetainedLayerTransaction(layerManager);
+ }
+
+ nsIFrame* frame = aBuilder->RootReferenceFrame();
+ nsPresContext* presContext = frame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+ nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+
+ NotifySubDocInvalidationFunc computeInvalidFunc =
+ presContext->MayHavePaintEventListenerInSubDocument() ? nsPresContext::NotifySubDocInvalidation : 0;
+ bool computeInvalidRect = (computeInvalidFunc ||
+ (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) &&
+ widgetTransaction;
+
+ UniquePtr<LayerProperties> props;
+ if (computeInvalidRect) {
+ props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
+ }
+
+ // Clear any ScrollMetadata that may have been set on the root layer on a
+ // previous paint. This paint will set new metrics if necessary, and if we
+ // don't clear the old one here, we may be left with extra metrics.
+ if (Layer* root = layerManager->GetRoot()) {
+ root->SetScrollMetadata(nsTArray<ScrollMetadata>());
+ }
+
+ ContainerLayerParameters containerParameters
+ (presShell->GetResolution(), presShell->GetResolution());
+
+ RefPtr<ContainerLayer> root;
+ {
+ PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
+ root = layerBuilder->
+ BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this,
+ containerParameters, nullptr);
+ }
+
+ nsIDocument* document = presShell->GetDocument();
+
+ if (!root) {
+ layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
+ return nullptr;
+ }
+ // Root is being scaled up by the X/Y resolution. Scale it back down.
+ root->SetPostScale(1.0f/containerParameters.mXScale,
+ 1.0f/containerParameters.mYScale);
+ root->SetScaleToResolution(presShell->ScaleToResolution(),
+ containerParameters.mXScale);
+ if (aBuilder->IsBuildingLayerEventRegions() &&
+ nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell)) {
+ root->SetEventRegionsOverride(EventRegionsOverride::ForceDispatchToContent);
+ } else {
+ root->SetEventRegionsOverride(EventRegionsOverride::NoOverride);
+ }
+
+ // If we're using containerless scrolling, there is still one case where we
+ // want the root container layer to have metrics. If the parent process is
+ // using XUL windows, there is no root scrollframe, and without explicitly
+ // creating metrics there will be no guaranteed top-level APZC.
+ bool addMetrics = gfxPrefs::LayoutUseContainersForRootFrames() ||
+ (XRE_IsParentProcess() && !presShell->GetRootScrollFrame());
+
+ // Add metrics if there are none in the layer tree with the id (create an id
+ // if there isn't one already) of the root scroll frame/root content.
+ bool ensureMetricsForRootId =
+ nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
+ !gfxPrefs::LayoutUseContainersForRootFrames() &&
+ aBuilder->IsPaintingToWindow() &&
+ !presContext->GetParentPresContext();
+
+ nsIContent* content = nullptr;
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ content = rootScrollFrame->GetContent();
+ } else {
+ // If there is no root scroll frame, pick the document element instead.
+ // The only case we don't want to do this is in non-APZ fennec, where
+ // we want the root xul document to get a null scroll id so that the root
+ // content document gets the first non-null scroll id.
+ content = document->GetDocumentElement();
+ }
+
+
+ if (ensureMetricsForRootId && content) {
+ ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
+ if (nsLayoutUtils::ContainsMetricsWithId(root, scrollId)) {
+ ensureMetricsForRootId = false;
+ }
+ }
+
+ if (addMetrics || ensureMetricsForRootId) {
+ bool isRootContent = presContext->IsRootContentDocument();
+
+ nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize());
+
+ root->SetScrollMetadata(
+ nsLayoutUtils::ComputeScrollMetadata(frame,
+ rootScrollFrame, content,
+ aBuilder->FindReferenceFrameFor(frame),
+ root, FrameMetrics::NULL_SCROLL_ID, viewport, Nothing(),
+ isRootContent, containerParameters));
+ }
+
+ // NS_WARNING is debug-only, so don't even bother checking the conditions in
+ // a release build.
+#ifdef DEBUG
+ bool usingDisplayport = false;
+ if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+ nsIContent* content = rootScrollFrame->GetContent();
+ if (content) {
+ usingDisplayport = nsLayoutUtils::HasDisplayPort(content);
+ }
+ }
+ if (usingDisplayport &&
+ !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
+ SpammyLayoutWarningsEnabled()) {
+ // See bug 693938, attachment 567017
+ NS_WARNING("Transparent content with displayports can be expensive.");
+ }
+#endif
+
+ layerManager->SetRoot(root);
+ layerBuilder->WillEndTransaction();
+
+ if (widgetTransaction ||
+ // SVG-as-an-image docs don't paint as part of the retained layer tree,
+ // but they still need the invalidation state bits cleared in order for
+ // invalidation for CSS/SMIL animation to work properly.
+ (document && document->IsBeingUsedAsImage())) {
+ frame->ClearInvalidationStateBits();
+ }
+
+ bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
+ LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT;
+ if (layerManager->NeedsWidgetInvalidation()) {
+ if (aFlags & PAINT_NO_COMPOSITE) {
+ flags = LayerManager::END_NO_COMPOSITE;
+ }
+ } else {
+ // Client layer managers never composite directly, so
+ // we don't need to worry about END_NO_COMPOSITE.
+ if (aBuilder->WillComputePluginGeometry()) {
+ flags = LayerManager::END_NO_REMOTE_COMPOSITE;
+ }
+ }
+
+ // If this is the content process, we ship plugin geometry updates over with layer
+ // updates, so calculate that now before we call EndTransaction.
+ if (rootPresContext && XRE_IsContentProcess()) {
+ if (aBuilder->WillComputePluginGeometry()) {
+ rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
+ }
+ // The layer system caches plugin configuration information for forwarding
+ // with layer updates which needs to get set during reflow. This must be
+ // called even if there are no windowed plugins in the page.
+ rootPresContext->CollectPluginGeometryUpdates(layerManager);
+ }
+
+ MaybeSetupTransactionIdAllocator(layerManager, view);
+
+ layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
+ aBuilder, flags);
+ aBuilder->SetIsCompositingCheap(temp);
+ layerBuilder->DidEndTransaction();
+
+ if (document && widgetTransaction) {
+ TriggerPendingAnimations(document, layerManager->GetAnimationReadyTime());
+ }
+
+ nsIntRegion invalid;
+ if (props) {
+ invalid = props->ComputeDifferences(root, computeInvalidFunc);
+ } else if (widgetTransaction) {
+ LayerProperties::ClearInvalidations(root);
+ }
+
+ bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
+ if (view) {
+ if (props) {
+ if (!invalid.IsEmpty()) {
+ nsIntRect bounds = invalid.GetBounds();
+ nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
+ presContext->DevPixelsToAppUnits(bounds.y),
+ presContext->DevPixelsToAppUnits(bounds.width),
+ presContext->DevPixelsToAppUnits(bounds.height));
+ if (shouldInvalidate) {
+ view->GetViewManager()->InvalidateViewNoSuppression(view, rect);
+ }
+ presContext->NotifyInvalidation(bounds, 0);
+ }
+ } else if (shouldInvalidate) {
+ view->GetViewManager()->InvalidateView(view);
+ }
+ }
+
+ layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
+ return layerManager.forget();
+}
+
+uint32_t nsDisplayList::Count() const {
+ uint32_t count = 0;
+ for (nsDisplayItem* i = GetBottom(); i; i = i->GetAbove()) {
+ ++count;
+ }
+ return count;
+}
+
+nsDisplayItem* nsDisplayList::RemoveBottom() {
+ nsDisplayItem* item = mSentinel.mAbove;
+ if (!item)
+ return nullptr;
+ mSentinel.mAbove = item->mAbove;
+ if (item == mTop) {
+ // must have been the only item
+ mTop = &mSentinel;
+ }
+ item->mAbove = nullptr;
+ return item;
+}
+
+void nsDisplayList::DeleteAll() {
+ nsDisplayItem* item;
+ while ((item = RemoveBottom()) != nullptr) {
+ item->~nsDisplayItem();
+ }
+}
+
+static bool
+GetMouseThrough(const nsIFrame* aFrame)
+{
+ if (!aFrame->IsXULBoxFrame())
+ return false;
+
+ const nsIFrame* frame = aFrame;
+ while (frame) {
+ if (frame->GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS) {
+ return true;
+ } else if (frame->GetStateBits() & NS_FRAME_MOUSE_THROUGH_NEVER) {
+ return false;
+ }
+ frame = nsBox::GetParentXULBox(frame);
+ }
+ return false;
+}
+
+static bool
+IsFrameReceivingPointerEvents(nsIFrame* aFrame)
+{
+ return NS_STYLE_POINTER_EVENTS_NONE !=
+ aFrame->StyleUserInterface()->GetEffectivePointerEvents(aFrame);
+}
+
+// A list of frames, and their z depth. Used for sorting
+// the results of hit testing.
+struct FramesWithDepth
+{
+ explicit FramesWithDepth(float aDepth) :
+ mDepth(aDepth)
+ {}
+
+ bool operator<(const FramesWithDepth& aOther) const {
+ if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) {
+ // We want to sort so that the shallowest item (highest depth value) is first
+ return mDepth > aOther.mDepth;
+ }
+ return this < &aOther;
+ }
+ bool operator==(const FramesWithDepth& aOther) const {
+ return this == &aOther;
+ }
+
+ float mDepth;
+ nsTArray<nsIFrame*> mFrames;
+};
+
+// Sort the frames by depth and then moves all the contained frames to the destination
+void FlushFramesArray(nsTArray<FramesWithDepth>& aSource, nsTArray<nsIFrame*>* aDest)
+{
+ if (aSource.IsEmpty()) {
+ return;
+ }
+ aSource.Sort();
+ uint32_t length = aSource.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ aDest->AppendElements(Move(aSource[i].mFrames));
+ }
+ aSource.Clear();
+}
+
+void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsDisplayItem::HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) const {
+ nsDisplayItem* item;
+
+ if (aState->mInPreserves3D) {
+ // Collect leaves of the current 3D rendering context.
+ for (item = GetBottom(); item; item = item->GetAbove()) {
+ auto itemType = item->GetType();
+ if (itemType != nsDisplayItem::TYPE_TRANSFORM ||
+ !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
+ item->HitTest(aBuilder, aRect, aState, aOutFrames);
+ } else {
+ // One of leaves in the current 3D rendering context.
+ aState->mItemBuffer.AppendElement(item);
+ }
+ }
+ return;
+ }
+
+ int32_t itemBufferStart = aState->mItemBuffer.Length();
+ for (item = GetBottom(); item; item = item->GetAbove()) {
+ aState->mItemBuffer.AppendElement(item);
+ }
+
+ AutoTArray<FramesWithDepth, 16> temp;
+ for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; --i) {
+ // Pop element off the end of the buffer. We want to shorten the buffer
+ // so that recursive calls to HitTest have more buffer space.
+ item = aState->mItemBuffer[i];
+ aState->mItemBuffer.SetLength(i);
+
+ bool snap;
+ nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect);
+ auto itemType = item->GetType();
+ bool same3DContext =
+ (itemType == nsDisplayItem::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) ||
+ (itemType == nsDisplayItem::TYPE_PERSPECTIVE &&
+ item->Frame()->Extend3DContext());
+ if (same3DContext &&
+ (itemType != nsDisplayItem::TYPE_TRANSFORM ||
+ !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) {
+ if (!item->GetClip().MayIntersect(aRect)) {
+ continue;
+ }
+ AutoTArray<nsIFrame*, 1> neverUsed;
+ // Start gethering leaves of the 3D rendering context, and
+ // append leaves at the end of mItemBuffer. Leaves are
+ // processed at following iterations.
+ aState->mInPreserves3D = true;
+ item->HitTest(aBuilder, aRect, aState, &neverUsed);
+ aState->mInPreserves3D = false;
+ i = aState->mItemBuffer.Length();
+ continue;
+ }
+ if (same3DContext || item->GetClip().MayIntersect(r)) {
+ AutoTArray<nsIFrame*, 16> outFrames;
+ item->HitTest(aBuilder, aRect, aState, &outFrames);
+
+ // For 3d transforms with preserve-3d we add hit frames into the temp list
+ // so we can sort them later, otherwise we add them directly to the output list.
+ nsTArray<nsIFrame*> *writeFrames = aOutFrames;
+ if (item->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
+ static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
+ if (outFrames.Length()) {
+ nsDisplayTransform *transform = static_cast<nsDisplayTransform*>(item);
+ nsPoint point = aRect.TopLeft();
+ // A 1x1 rect means a point, otherwise use the center of the rect
+ if (aRect.width != 1 || aRect.height != 1) {
+ point = aRect.Center();
+ }
+ temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point)));
+ writeFrames = &temp[temp.Length() - 1].mFrames;
+ }
+ } else {
+ // We may have just finished a run of consecutive preserve-3d transforms,
+ // so flush these into the destination array before processing our frame list.
+ FlushFramesArray(temp, aOutFrames);
+ }
+
+ for (uint32_t j = 0; j < outFrames.Length(); j++) {
+ nsIFrame *f = outFrames.ElementAt(j);
+ // Handle the XUL 'mousethrough' feature and 'pointer-events'.
+ if (!GetMouseThrough(f) && IsFrameReceivingPointerEvents(f)) {
+ writeFrames->AppendElement(f);
+ }
+ }
+ }
+ }
+ // Clear any remaining preserve-3d transforms.
+ FlushFramesArray(temp, aOutFrames);
+ NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart),
+ "How did we forget to pop some elements?");
+}
+
+static void Sort(nsDisplayList* aList, int32_t aCount, nsDisplayList::SortLEQ aCmp,
+ void* aClosure) {
+ if (aCount < 2)
+ return;
+
+ nsDisplayList list1;
+ nsDisplayList list2;
+ int i;
+ int32_t half = aCount/2;
+ bool sorted = true;
+ nsDisplayItem* prev = nullptr;
+ for (i = 0; i < aCount; ++i) {
+ nsDisplayItem* item = aList->RemoveBottom();
+ (i < half ? &list1 : &list2)->AppendToTop(item);
+ if (sorted && prev && !aCmp(prev, item, aClosure)) {
+ sorted = false;
+ }
+ prev = item;
+ }
+ if (sorted) {
+ aList->AppendToTop(&list1);
+ aList->AppendToTop(&list2);
+ return;
+ }
+
+ Sort(&list1, half, aCmp, aClosure);
+ Sort(&list2, aCount - half, aCmp, aClosure);
+
+ for (i = 0; i < aCount; ++i) {
+ if (list1.GetBottom() &&
+ (!list2.GetBottom() ||
+ aCmp(list1.GetBottom(), list2.GetBottom(), aClosure))) {
+ aList->AppendToTop(list1.RemoveBottom());
+ } else {
+ aList->AppendToTop(list2.RemoveBottom());
+ }
+ }
+}
+
+static nsIContent* FindContentInDocument(nsDisplayItem* aItem, nsIDocument* aDoc) {
+ nsIFrame* f = aItem->Frame();
+ while (f) {
+ nsPresContext* pc = f->PresContext();
+ if (pc->Document() == aDoc) {
+ return f->GetContent();
+ }
+ f = nsLayoutUtils::GetCrossDocParentFrame(pc->PresShell()->GetRootFrame());
+ }
+ return nullptr;
+}
+
+static bool IsContentLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
+ void* aClosure) {
+ nsIContent* commonAncestor = static_cast<nsIContent*>(aClosure);
+ // It's possible that the nsIContent for aItem1 or aItem2 is in a subdocument
+ // of commonAncestor, because display items for subdocuments have been
+ // mixed into the same list. Ensure that we're looking at content
+ // in commonAncestor's document.
+ nsIDocument* commonAncestorDoc = commonAncestor->OwnerDoc();
+ nsIContent* content1 = FindContentInDocument(aItem1, commonAncestorDoc);
+ nsIContent* content2 = FindContentInDocument(aItem2, commonAncestorDoc);
+ if (!content1 || !content2) {
+ NS_ERROR("Document trees are mixed up!");
+ // Something weird going on
+ return true;
+ }
+ return nsLayoutUtils::CompareTreePosition(content1, content2, commonAncestor) <= 0;
+}
+
+static bool IsZOrderLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
+ void* aClosure) {
+ // Note that we can't just take the difference of the two
+ // z-indices here, because that might overflow a 32-bit int.
+ return aItem1->ZIndex() <= aItem2->ZIndex();
+}
+
+void nsDisplayList::SortByZOrder() {
+ Sort(IsZOrderLEQ, nullptr);
+}
+
+void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) {
+ Sort(IsContentLEQ, aCommonAncestor);
+}
+
+void nsDisplayList::Sort(SortLEQ aCmp, void* aClosure) {
+ ::Sort(this, Count(), aCmp, aClosure);
+}
+
+nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame, aBuilder->ClipState().GetCurrentInnermostScrollClip())
+{}
+
+nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const DisplayItemScrollClip* aScrollClip)
+ : mFrame(aFrame)
+ , mClip(aBuilder->ClipState().GetCurrentCombinedClip(aBuilder))
+ , mScrollClip(aScrollClip)
+ , mAnimatedGeometryRoot(nullptr)
+ , mForceNotVisible(aBuilder->IsBuildingInvisibleItems())
+#ifdef MOZ_DUMP_PAINTING
+ , mPainted(false)
+#endif
+{
+ mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
+ // This can return the wrong result if the item override ShouldFixToViewport(),
+ // the item needs to set it again in its constructor.
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame);
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(),
+ *mAnimatedGeometryRoot), "Bad");
+ NS_ASSERTION(aBuilder->GetDirtyRect().width >= 0 ||
+ !aBuilder->IsForPainting(), "dirty rect not set");
+ // The dirty rect is for mCurrentFrame, so we have to use
+ // mCurrentOffsetToReferenceFrame
+ mVisibleRect = aBuilder->GetDirtyRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+}
+
+/* static */ bool
+nsDisplayItem::ForceActiveLayers()
+{
+ static bool sForce = false;
+ static bool sForceCached = false;
+
+ if (!sForceCached) {
+ Preferences::AddBoolVarCache(&sForce, "layers.force-active", false);
+ sForceCached = true;
+ }
+
+ return sForce;
+}
+
+static int32_t ZIndexForFrame(nsIFrame* aFrame)
+{
+ if (!aFrame->IsAbsPosContainingBlock() && !aFrame->IsFlexOrGridItem())
+ return 0;
+
+ const nsStylePosition* position = aFrame->StylePosition();
+ if (position->mZIndex.GetUnit() == eStyleUnit_Integer)
+ return position->mZIndex.GetIntValue();
+
+ // sort the auto and 0 elements together
+ return 0;
+}
+
+int32_t
+nsDisplayItem::ZIndex() const
+{
+ return ZIndexForFrame(mFrame);
+}
+
+bool
+nsDisplayItem::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion)
+{
+ return !mVisibleRect.IsEmpty() &&
+ !IsInvisibleInRect(aVisibleRegion->GetBounds());
+}
+
+bool
+nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (mForceNotVisible && !GetSameCoordinateSystemChildren()) {
+ // mForceNotVisible wants to ensure that this display item doesn't render
+ // anything itself. If this item has contents, then we obviously want to
+ // render those, so we don't need this check in that case.
+ NS_ASSERTION(mVisibleRect.IsEmpty(),
+ "invisible items without children should have empty vis rect");
+ } else {
+ nsRect bounds = GetClippedBounds(aBuilder);
+
+ nsRegion itemVisible;
+ itemVisible.And(*aVisibleRegion, bounds);
+ mVisibleRect = itemVisible.GetBounds();
+ }
+
+ // When we recompute visibility within layers we don't need to
+ // expand the visible region for content behind plugins (the plugin
+ // is not in the layer).
+ if (!ComputeVisibility(aBuilder, aVisibleRegion)) {
+ mVisibleRect = nsRect();
+ return false;
+ }
+
+ nsRegion opaque = TreatAsOpaque(this, aBuilder);
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque);
+ return true;
+}
+
+nsRect
+nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder)
+{
+ bool snap;
+ nsRect r = GetBounds(aBuilder, &snap);
+ return GetClip().ApplyNonRoundedIntersection(r);
+}
+
+nsRect
+nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = true;
+ return mBounds;
+}
+
+void
+nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect =
+ NSRectToSnappedRect(mVisibleRect, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor)));
+}
+
+void
+nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (rgba "
+ << (int)NS_GET_R(mColor) << ","
+ << (int)NS_GET_G(mColor) << ","
+ << (int)NS_GET_B(mColor) << ","
+ << (int)NS_GET_A(mColor) << ")";
+}
+
+nsRect
+nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = true;
+ return mRegion.GetBounds();
+}
+
+void
+nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ ColorPattern color(mColor);
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ Rect rect =
+ NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(rect, color);
+ }
+}
+
+void
+nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (rgba "
+ << int(mColor.r * 255) << ","
+ << int(mColor.g * 255) << ","
+ << int(mColor.b * 255) << ","
+ << mColor.a << ")";
+}
+
+static void
+RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsITheme::ThemeGeometryType aType)
+{
+ if (aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) {
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ nsPoint offset = aBuilder->IsInSubdocument() ? aBuilder->ToReferenceFrame(aFrame)
+ : aFrame->GetOffsetTo(displayRoot);
+ nsRect borderBox = nsRect(offset, aFrame->GetSize());
+ aBuilder->RegisterThemeGeometry(aType,
+ LayoutDeviceIntRect::FromUnknownRect(
+ borderBox.ToNearestPixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel())));
+ }
+}
+
+// Return the bounds of the viewport relative to |aFrame|'s reference frame.
+// Returns Nothing() if transforming into |aFrame|'s coordinate space fails.
+static Maybe<nsRect>
+GetViewportRectRelativeToReferenceFrame(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+{
+ nsIFrame* rootFrame = aFrame->PresContext()->PresShell()->GetRootFrame();
+ nsRect rootRect = rootFrame->GetRectRelativeToSelf();
+ if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return Some(rootRect + aBuilder->ToReferenceFrame(aFrame));
+ }
+ return Nothing();
+}
+
+nsDisplayBackgroundImage::nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ uint32_t aLayer,
+ const nsRect& aBackgroundRect,
+ const nsStyleBackground* aBackgroundStyle)
+ : nsDisplayImageContainer(aBuilder, aFrame)
+ , mBackgroundStyle(aBackgroundStyle)
+ , mBackgroundRect(aBackgroundRect)
+ , mLayer(aLayer)
+ , mIsRasterImage(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
+
+ nsPresContext* presContext = mFrame->PresContext();
+ uint32_t flags = aBuilder->GetBackgroundPaintFlags();
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+
+ bool isTransformedFixed;
+ nsBackgroundLayerState state =
+ nsCSSRendering::PrepareImageLayer(presContext, mFrame, flags,
+ mBackgroundRect, mBackgroundRect, layer,
+ &isTransformedFixed);
+ mShouldTreatAsFixed = ComputeShouldTreatAsFixed(isTransformedFixed);
+
+ mBounds = GetBoundsInternal(aBuilder);
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
+
+ // Expand the item's visible rect to cover the entire bounds, limited to the
+ // viewport rect. This is necessary because the background's clip can move
+ // asynchronously.
+ if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame(aBuilder, mFrame)) {
+ mVisibleRect = mBounds.Intersect(*viewportRect);
+ }
+ }
+
+ mFillRect = state.mFillArea;
+ mDestRect = state.mDestArea;
+
+ nsImageRenderer* imageRenderer = &state.mImageRenderer;
+ // We only care about images here, not gradients.
+ if (imageRenderer->IsRasterImage()) {
+ mIsRasterImage = true;
+ mImage = imageRenderer->GetImage();
+ }
+}
+
+nsDisplayBackgroundImage::~nsDisplayBackgroundImage()
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNT_DTOR(nsDisplayBackgroundImage);
+#endif
+}
+
+static nsStyleContext* GetBackgroundStyleContext(nsIFrame* aFrame)
+{
+ nsStyleContext *sc;
+ if (!nsCSSRendering::FindBackground(aFrame, &sc)) {
+ // We don't want to bail out if moz-appearance is set on a root
+ // node. If it has a parent content node, bail because it's not
+ // a root, other wise keep going in order to let the theme stuff
+ // draw the background. The canvas really should be drawing the
+ // bg, but there's no way to hook that up via css.
+ if (!aFrame->StyleDisplay()->mAppearance) {
+ return nullptr;
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || content->GetParent()) {
+ return nullptr;
+ }
+
+ sc = aFrame->StyleContext();
+ }
+ return sc;
+}
+
+/* static */ void
+SetBackgroundClipRegion(DisplayListClipState::AutoSaveRestore& aClipState,
+ nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
+ const nsStyleImageLayers::Layer& aLayer,
+ const nsRect& aBackgroundRect,
+ bool aWillPaintBorder)
+{
+ nsCSSRendering::ImageLayerClipState clip;
+ nsCSSRendering::GetImageLayerClip(aLayer, aFrame, *aFrame->StyleBorder(),
+ aBackgroundRect, aBackgroundRect, aWillPaintBorder,
+ aFrame->PresContext()->AppUnitsPerDevPixel(),
+ &clip);
+
+ if (clip.mHasAdditionalBGClipArea) {
+ aClipState.ClipContentDescendants(clip.mAdditionalBGClipArea, clip.mBGClipArea,
+ clip.mHasRoundedCorners ? clip.mRadii : nullptr);
+ } else {
+ aClipState.ClipContentDescendants(clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr);
+ }
+}
+
+/**
+ * This is used for the find bar highlighter overlay. It's only accessible
+ * through the AnonymousContent API, so it's not exposed to general web pages.
+ */
+static bool
+SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ nsDisplayList* aList,
+ nscolor aColor)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ return false;
+ }
+
+ void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion);
+ if (!cutoutRegion) {
+ return false;
+ }
+
+ if (NS_GET_A(aColor) == 0) {
+ return true;
+ }
+
+ nsRegion region;
+ region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion));
+ region.MoveBy(aBuilder->ToReferenceFrame(aFrame));
+ aList->AppendNewToTop(
+ new (aBuilder) nsDisplaySolidColorRegion(aBuilder, aFrame, region, aColor));
+
+ return true;
+}
+
+
+/*static*/ bool
+nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization,
+ nsStyleContext* aStyleContext)
+{
+ nsStyleContext* bgSC = aStyleContext;
+ const nsStyleBackground* bg = nullptr;
+ nsRect bgRect = aBackgroundRect + aBuilder->ToReferenceFrame(aFrame);
+ nsPresContext* presContext = aFrame->PresContext();
+ bool isThemed = aFrame->IsThemed();
+ if (!isThemed) {
+ if (!bgSC) {
+ bgSC = GetBackgroundStyleContext(aFrame);
+ }
+ if (bgSC) {
+ bg = bgSC->StyleBackground();
+ }
+ }
+
+ bool drawBackgroundColor = false;
+ // Dummy initialisation to keep Valgrind/Memcheck happy.
+ // See bug 1122375 comment 1.
+ nscolor color = NS_RGBA(0,0,0,0);
+ if (!nsCSSRendering::IsCanvasFrame(aFrame) && bg) {
+ bool drawBackgroundImage;
+ color =
+ nsCSSRendering::DetermineBackgroundColor(presContext, bgSC, aFrame,
+ drawBackgroundImage, drawBackgroundColor);
+ }
+
+ if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList, color)) {
+ return false;
+ }
+
+ const nsStyleBorder* borderStyle = aFrame->StyleBorder();
+ const nsStyleEffects* effectsStyle = aFrame->StyleEffects();
+ bool hasInsetShadow = effectsStyle->mBoxShadow &&
+ effectsStyle->mBoxShadow->HasShadowWithInset(true);
+ bool willPaintBorder = aAllowWillPaintBorderOptimization &&
+ !isThemed && !hasInsetShadow &&
+ borderStyle->HasBorder();
+
+ nsPoint toRef = aBuilder->ToReferenceFrame(aFrame);
+
+ // An auxiliary list is necessary in case we have background blending; if that
+ // is the case, background items need to be wrapped by a blend container to
+ // isolate blending to the background
+ nsDisplayList bgItemList;
+ // Even if we don't actually have a background color to paint, we may still need
+ // to create an item for hit testing.
+ if ((drawBackgroundColor && color != NS_RGBA(0,0,0,0)) ||
+ aBuilder->IsForEventDelivery()) {
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ if (bg && !aBuilder->IsForEventDelivery()) {
+ // Disable the will-paint-border optimization for background
+ // colors with no border-radius. Enabling it for background colors
+ // doesn't help much (there are no tiling issues) and clipping the
+ // background breaks detection of the element's border-box being
+ // opaque. For nonzero border-radius we still need it because we
+ // want to inset the background if possible to avoid antialiasing
+ // artifacts along the rounded corners.
+ bool useWillPaintBorderOptimization = willPaintBorder &&
+ nsLayoutUtils::HasNonZeroCorner(borderStyle->mBorderRadius);
+ SetBackgroundClipRegion(clipState, aFrame, toRef,
+ bg->BottomLayer(), bgRect,
+ useWillPaintBorderOptimization);
+ }
+ bgItemList.AppendNewToTop(
+ new (aBuilder) nsDisplayBackgroundColor(aBuilder, aFrame, bgRect, bg,
+ drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0)));
+ }
+
+ if (isThemed) {
+ nsITheme* theme = presContext->GetTheme();
+ if (theme->NeedToClearBackgroundBehindWidget(aFrame, aFrame->StyleDisplay()->mAppearance) &&
+ aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) {
+ bgItemList.AppendNewToTop(
+ new (aBuilder) nsDisplayClearBackground(aBuilder, aFrame));
+ }
+ nsDisplayThemedBackground* bgItem =
+ new (aBuilder) nsDisplayThemedBackground(aBuilder, aFrame, bgRect);
+ bgItemList.AppendNewToTop(bgItem);
+ aList->AppendToTop(&bgItemList);
+ return true;
+ }
+
+ if (!bg) {
+ aList->AppendToTop(&bgItemList);
+ return false;
+ }
+
+ const DisplayItemScrollClip* scrollClip =
+ aBuilder->ClipState().GetCurrentInnermostScrollClip();
+
+ bool needBlendContainer = false;
+
+ // Passing bg == nullptr in this macro will result in one iteration with
+ // i = 0.
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) {
+ if (bg->mImage.mLayers[i].mImage.IsEmpty()) {
+ continue;
+ }
+
+ if (bg->mImage.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) {
+ needBlendContainer = true;
+ }
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ if (!aBuilder->IsForEventDelivery()) {
+ const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i];
+ SetBackgroundClipRegion(clipState, aFrame, toRef,
+ layer, bgRect, willPaintBorder);
+ }
+
+ nsDisplayList thisItemList;
+ nsDisplayBackgroundImage* bgItem =
+ new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgRect, bg);
+
+ if (bgItem->ShouldFixToViewport(aBuilder)) {
+ thisItemList.AppendNewToTop(
+ nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, aFrame, bgItem, i));
+ } else {
+ thisItemList.AppendNewToTop(bgItem);
+ }
+
+ if (bg->mImage.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) {
+ thisItemList.AppendNewToTop(
+ new (aBuilder) nsDisplayBlendMode(aBuilder, aFrame, &thisItemList,
+ bg->mImage.mLayers[i].mBlendMode,
+ scrollClip, i + 1));
+ }
+ bgItemList.AppendToTop(&thisItemList);
+ }
+
+ if (needBlendContainer) {
+ bgItemList.AppendNewToTop(
+ nsDisplayBlendContainer::CreateForBackgroundBlendMode(aBuilder, aFrame, &bgItemList, scrollClip));
+ }
+
+ aList->AppendToTop(&bgItemList);
+ return false;
+}
+
+// Check that the rounded border of aFrame, added to aToReferenceFrame,
+// intersects aRect. Assumes that the unrounded border has already
+// been checked for intersection.
+static bool
+RoundedBorderIntersectsRect(nsIFrame* aFrame,
+ const nsPoint& aFrameToReferenceFrame,
+ const nsRect& aTestRect)
+{
+ if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize()).Intersects(aTestRect))
+ return false;
+
+ nscoord radii[8];
+ return !aFrame->GetBorderRadii(radii) ||
+ nsLayoutUtils::RoundedRectIntersectsRect(nsRect(aFrameToReferenceFrame,
+ aFrame->GetSize()),
+ radii, aTestRect);
+}
+
+// Returns TRUE if aContainedRect is guaranteed to be contained in
+// the rounded rect defined by aRoundedRect and aRadii. Complex cases are
+// handled conservatively by returning FALSE in some situations where
+// a more thorough analysis could return TRUE.
+//
+// See also RoundedRectIntersectsRect.
+static bool RoundedRectContainsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect) {
+ nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii, aContainedRect);
+ return rgn.Contains(aContainedRect);
+}
+
+bool
+nsDisplayBackgroundImage::ShouldTreatAsFixed() const
+{
+ return mShouldTreatAsFixed;
+}
+
+bool
+nsDisplayBackgroundImage::ComputeShouldTreatAsFixed(bool isTransformedFixed) const
+{
+ if (!mBackgroundStyle)
+ return false;
+
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ if (layer.mAttachment != NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED)
+ return false;
+
+ // background-attachment:fixed is treated as background-attachment:scroll
+ // if it's affected by a transform.
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521.
+ return !isTransformedFixed;
+}
+
+bool
+nsDisplayBackgroundImage::IsNonEmptyFixedImage() const
+{
+ return ShouldTreatAsFixed() &&
+ !mBackgroundStyle->mImage.mLayers[mLayer].mImage.IsEmpty();
+}
+
+bool
+nsDisplayBackgroundImage::ShouldFixToViewport(nsDisplayListBuilder* aBuilder)
+{
+ // APZ needs background-attachment:fixed images layerized for correctness.
+ RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager();
+ if (!nsLayoutUtils::UsesAsyncScrolling(mFrame) &&
+ layerManager && layerManager->ShouldAvoidComponentAlphaLayers()) {
+ return false;
+ }
+
+ // Put background-attachment:fixed background images in their own
+ // compositing layer.
+ return IsNonEmptyFixedImage();
+}
+
+bool
+nsDisplayBackgroundImage::CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder)
+{
+ if (!mBackgroundStyle) {
+ return false;
+ }
+
+ // We currently can't handle tiled backgrounds.
+ if (!mDestRect.Contains(mFillRect)) {
+ return false;
+ }
+
+ // For 'contain' and 'cover', we allow any pixel of the image to be sampled
+ // because there isn't going to be any spriting/atlasing going on.
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ bool allowPartialImages =
+ (layer.mSize.mWidthType == nsStyleImageLayers::Size::eContain ||
+ layer.mSize.mWidthType == nsStyleImageLayers::Size::eCover);
+ if (!allowPartialImages && !mFillRect.Contains(mDestRect)) {
+ return false;
+ }
+
+ return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder);
+}
+
+nsRect
+nsDisplayBackgroundImage::GetDestRect()
+{
+ return mDestRect;
+}
+
+already_AddRefed<imgIContainer>
+nsDisplayBackgroundImage::GetImage()
+{
+ nsCOMPtr<imgIContainer> image = mImage;
+ return image.forget();
+}
+
+nsDisplayBackgroundImage::ImageLayerization
+nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager)
+{
+ nsIFrame* backgroundStyleFrame = nsCSSRendering::FindBackgroundStyleFrame(mFrame);
+ if (ActiveLayerTracker::IsBackgroundPositionAnimated(aBuilder,
+ backgroundStyleFrame)) {
+ return WHENEVER_POSSIBLE;
+ }
+
+ if (nsLayoutUtils::AnimatedImageLayersEnabled() && mBackgroundStyle) {
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ const nsStyleImage* image = &layer.mImage;
+ if (image->GetType() == eStyleImageType_Image) {
+ imgIRequest* imgreq = image->GetImageData();
+ nsCOMPtr<imgIContainer> image;
+ if (imgreq &&
+ NS_SUCCEEDED(imgreq->GetImage(getter_AddRefs(image))) &&
+ image) {
+ bool animated = false;
+ if (NS_SUCCEEDED(image->GetAnimated(&animated)) && animated) {
+ return WHENEVER_POSSIBLE;
+ }
+ }
+ }
+ }
+
+ if (nsLayoutUtils::GPUImageScalingEnabled() &&
+ aManager->IsCompositingCheap()) {
+ return ONLY_FOR_SCALING;
+ }
+
+ return NO_LAYER_NEEDED;
+}
+
+LayerState
+nsDisplayBackgroundImage::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ ImageLayerization shouldLayerize = ShouldCreateOwnLayer(aBuilder, aManager);
+ if (shouldLayerize == NO_LAYER_NEEDED) {
+ // We can skip the call to CanOptimizeToImageLayer if we don't want a
+ // layer anyway.
+ return LAYER_NONE;
+ }
+
+ if (CanOptimizeToImageLayer(aManager, aBuilder)) {
+ if (shouldLayerize == WHENEVER_POSSIBLE) {
+ return LAYER_ACTIVE;
+ }
+
+ MOZ_ASSERT(shouldLayerize == ONLY_FOR_SCALING, "unhandled ImageLayerization value?");
+
+ MOZ_ASSERT(mImage);
+ int32_t imageWidth;
+ int32_t imageHeight;
+ mImage->GetWidth(&imageWidth);
+ mImage->GetHeight(&imageHeight);
+ NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
+
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(GetDestRect(), appUnitsPerDevPixel);
+
+ const LayerRect destLayerRect = destRect * aParameters.Scale();
+
+ // Calculate the scaling factor for the frame.
+ const gfxSize scale = gfxSize(destLayerRect.width / imageWidth,
+ destLayerRect.height / imageHeight);
+
+ if ((scale.width != 1.0f || scale.height != 1.0f) &&
+ (destLayerRect.width * destLayerRect.height >= 64 * 64)) {
+ // Separate this image into a layer.
+ // There's no point in doing this if we are not scaling at all or if the
+ // target size is pretty small.
+ return LAYER_ACTIVE;
+ }
+ }
+
+ return LAYER_NONE;
+}
+
+already_AddRefed<Layer>
+nsDisplayBackgroundImage::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ RefPtr<ImageLayer> layer = static_cast<ImageLayer*>
+ (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
+ if (!layer) {
+ layer = aManager->CreateImageLayer();
+ if (!layer)
+ return nullptr;
+ }
+ RefPtr<ImageContainer> imageContainer = GetContainer(aManager, aBuilder);
+ layer->SetContainer(imageContainer);
+ ConfigureLayer(layer, aParameters);
+ return layer.forget();
+}
+
+void
+nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+}
+
+bool
+nsDisplayBackgroundImage::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion)
+{
+ if (!nsDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ // Return false if the background was propagated away from this
+ // frame. We don't want this display item to show up and confuse
+ // anything.
+ return mBackgroundStyle;
+}
+
+/* static */ nsRegion
+nsDisplayBackgroundImage::GetInsideClipRegion(nsDisplayItem* aItem,
+ uint8_t aClip,
+ const nsRect& aRect,
+ const nsRect& aBackgroundRect)
+{
+ nsRegion result;
+ if (aRect.IsEmpty())
+ return result;
+
+ nsIFrame *frame = aItem->Frame();
+
+ nsRect clipRect = aBackgroundRect;
+ if (frame->GetType() == nsGkAtoms::canvasFrame) {
+ nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
+ clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame();
+ } else if (aClip == NS_STYLE_IMAGELAYER_CLIP_PADDING ||
+ aClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT) {
+ nsMargin border = frame->GetUsedBorder();
+ if (aClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT) {
+ border += frame->GetUsedPadding();
+ }
+ border.ApplySkipSides(frame->GetSkipSides());
+ clipRect.Deflate(border);
+ }
+
+ return clipRect.Intersect(aRect);
+}
+
+nsRegion
+nsDisplayBackgroundImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) {
+ nsRegion result;
+ *aSnap = false;
+
+ if (!mBackgroundStyle)
+ return result;
+
+ *aSnap = true;
+
+ // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since
+ // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
+ // which expects frames to be sent to it in content order, not reverse
+ // content order which we'll produce here.
+ // Of course, if there's only one frame in the flow, it doesn't matter.
+ if (mFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone ||
+ (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
+ const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL &&
+ layer.mRepeat.mXRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
+ layer.mRepeat.mYRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
+ layer.mClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect);
+ }
+ }
+
+ return result;
+}
+
+Maybe<nscolor>
+nsDisplayBackgroundImage::IsUniform(nsDisplayListBuilder* aBuilder) {
+ if (!mBackgroundStyle) {
+ return Some(NS_RGBA(0,0,0,0));
+ }
+ return Nothing();
+}
+
+nsRect
+nsDisplayBackgroundImage::GetPositioningArea()
+{
+ if (!mBackgroundStyle) {
+ return nsRect();
+ }
+ nsIFrame* attachedToFrame;
+ bool transformedFixed;
+ return nsCSSRendering::ComputeImageLayerPositioningArea(
+ mFrame->PresContext(), mFrame,
+ mBackgroundRect,
+ mBackgroundStyle->mImage.mLayers[mLayer],
+ &attachedToFrame,
+ &transformedFixed) + ToReferenceFrame();
+}
+
+bool
+nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange()
+{
+ if (!mBackgroundStyle)
+ return false;
+
+ nscoord radii[8];
+ if (mFrame->GetBorderRadii(radii)) {
+ // A change in the size of the positioning area might change the position
+ // of the rounded corners.
+ return true;
+ }
+
+ const nsStyleImageLayers::Layer &layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ if (layer.RenderingMightDependOnPositioningAreaSizeChange()) {
+ return true;
+ }
+ return false;
+}
+
+static void CheckForBorderItem(nsDisplayItem *aItem, uint32_t& aFlags)
+{
+ nsDisplayItem* nextItem = aItem->GetAbove();
+ while (nextItem && nextItem->GetType() == nsDisplayItem::TYPE_BACKGROUND) {
+ nextItem = nextItem->GetAbove();
+ }
+ if (nextItem &&
+ nextItem->Frame() == aItem->Frame() &&
+ nextItem->GetType() == nsDisplayItem::TYPE_BORDER) {
+ aFlags |= nsCSSRendering::PAINTBG_WILL_PAINT_BORDER;
+ }
+}
+
+void
+nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ PaintInternal(aBuilder, aCtx, mVisibleRect, &mBounds);
+}
+
+void
+nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx, const nsRect& aBounds,
+ nsRect* aClipRect) {
+ uint32_t flags = aBuilder->GetBackgroundPaintFlags();
+ CheckForBorderItem(this, flags);
+
+ gfxContext* ctx = aCtx->ThebesContext();
+ uint8_t clip = mBackgroundStyle->mImage.mLayers[mLayer].mClip;
+
+ if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) {
+ return;
+ }
+ }
+
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(*mFrame->PresContext(),
+ *aCtx,
+ aBounds, mBackgroundRect,
+ mFrame, flags, mLayer,
+ CompositionOp::OP_OVER);
+ params.bgClipRect = aClipRect;
+ image::DrawResult result =
+ nsCSSRendering::PaintBackground(params);
+
+ if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ ctx->PopGroupAndBlend();
+ }
+
+ nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
+}
+
+void nsDisplayBackgroundImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ if (!mBackgroundStyle) {
+ return;
+ }
+
+ const nsDisplayBackgroundGeometry* geometry = static_cast<const nsDisplayBackgroundGeometry*>(aGeometry);
+
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRect positioningArea = GetPositioningArea();
+ if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() ||
+ (positioningArea.Size() != geometry->mPositioningArea.Size() &&
+ RenderingMightDependOnPositioningAreaSizeChange())) {
+ // Positioning area changed in a way that could cause everything to change,
+ // so invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+
+ if (positioningArea.Size() != geometry->mPositioningArea.Size()) {
+ NotifyRenderingChanged();
+ }
+ return;
+ }
+ if (!mDestRect.IsEqualInterior(geometry->mDestRect)) {
+ // Dest area changed in a way that could cause everything to change,
+ // so invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ NotifyRenderingChanged();
+ return;
+ }
+ if (aBuilder->ShouldSyncDecodeImages()) {
+ const nsStyleImage& image = mBackgroundStyle->mImage.mLayers[mLayer].mImage;
+ if (image.GetType() == eStyleImageType_Image &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+
+ NotifyRenderingChanged();
+ }
+ }
+ if (!bounds.IsEqualInterior(geometry->mBounds)) {
+ // Positioning area is unchanged, so invalidate just the change in the
+ // painting area.
+ aInvalidRegion->Xor(bounds, geometry->mBounds);
+
+ NotifyRenderingChanged();
+ }
+}
+
+nsRect
+nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = true;
+ return mBounds;
+}
+
+nsRect
+nsDisplayBackgroundImage::GetBoundsInternal(nsDisplayListBuilder* aBuilder) {
+ nsPresContext* presContext = mFrame->PresContext();
+
+ if (!mBackgroundStyle) {
+ return nsRect();
+ }
+
+ nsRect clipRect = mBackgroundRect;
+ if (mFrame->GetType() == nsGkAtoms::canvasFrame) {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ clipRect = frame->CanvasArea() + ToReferenceFrame();
+ } else if (nsLayoutUtils::UsesAsyncScrolling(mFrame) && IsNonEmptyFixedImage()) {
+ // If this is a background-attachment:fixed image, and APZ is enabled,
+ // async scrolling could reveal additional areas of the image, so don't
+ // clip it beyond clipping to the document's viewport.
+ if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame(aBuilder, mFrame)) {
+ clipRect = clipRect.Union(*viewportRect);
+ }
+ }
+ const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
+ return nsCSSRendering::GetBackgroundLayerRect(presContext, mFrame,
+ mBackgroundRect, clipRect, layer,
+ aBuilder->GetBackgroundPaintFlags());
+}
+
+uint32_t
+nsDisplayBackgroundImage::GetPerFrameKey()
+{
+ return (mLayer << nsDisplayItem::TYPE_BITS) |
+ nsDisplayItem::GetPerFrameKey();
+}
+
+nsDisplayThemedBackground::nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mBackgroundRect(aBackgroundRect)
+{
+ MOZ_COUNT_CTOR(nsDisplayThemedBackground);
+
+ const nsStyleDisplay* disp = mFrame->StyleDisplay();
+ mAppearance = disp->mAppearance;
+ mFrame->IsThemed(disp, &mThemeTransparency);
+
+ // Perform necessary RegisterThemeGeometry
+ nsITheme* theme = mFrame->PresContext()->GetTheme();
+ nsITheme::ThemeGeometryType type =
+ theme->ThemeGeometryTypeForWidget(mFrame, disp->mAppearance);
+ if (type != nsITheme::eThemeGeometryTypeUnknown) {
+ RegisterThemeGeometry(aBuilder, aFrame, type);
+ }
+
+ if (disp->mAppearance == NS_THEME_WIN_BORDERLESS_GLASS ||
+ disp->mAppearance == NS_THEME_WIN_GLASS) {
+ aBuilder->SetGlassDisplayItem(this);
+ }
+
+ mBounds = GetBoundsInternal();
+}
+
+nsDisplayThemedBackground::~nsDisplayThemedBackground()
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNT_DTOR(nsDisplayThemedBackground);
+#endif
+}
+
+void
+nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (themed, appearance:" << (int)mAppearance << ")";
+}
+
+void
+nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ // Assume that any point in our background rect is a hit.
+ if (mBackgroundRect.Intersects(aRect)) {
+ aOutFrames->AppendElement(mFrame);
+ }
+}
+
+nsRegion
+nsDisplayThemedBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) {
+ nsRegion result;
+ *aSnap = false;
+
+ if (mThemeTransparency == nsITheme::eOpaque) {
+ result = mBackgroundRect;
+ }
+ return result;
+}
+
+Maybe<nscolor>
+nsDisplayThemedBackground::IsUniform(nsDisplayListBuilder* aBuilder) {
+ if (mAppearance == NS_THEME_WIN_BORDERLESS_GLASS ||
+ mAppearance == NS_THEME_WIN_GLASS) {
+ return Some(NS_RGBA(0,0,0,0));
+ }
+ return Nothing();
+}
+
+bool
+nsDisplayThemedBackground::ProvidesFontSmoothingBackgroundColor(nscolor* aColor)
+{
+ nsITheme* theme = mFrame->PresContext()->GetTheme();
+ return theme->WidgetProvidesFontSmoothingBackgroundColor(mFrame, mAppearance, aColor);
+}
+
+nsRect
+nsDisplayThemedBackground::GetPositioningArea()
+{
+ return mBackgroundRect;
+}
+
+void
+nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ PaintInternal(aBuilder, aCtx, mVisibleRect, nullptr);
+}
+
+
+void
+nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx, const nsRect& aBounds,
+ nsRect* aClipRect)
+{
+ // XXXzw this ignores aClipRect.
+ nsPresContext* presContext = mFrame->PresContext();
+ nsITheme *theme = presContext->GetTheme();
+ nsRect drawing(mBackgroundRect);
+ theme->GetWidgetOverflow(presContext->DeviceContext(), mFrame, mAppearance,
+ &drawing);
+ drawing.IntersectRect(drawing, aBounds);
+ theme->DrawWidgetBackground(aCtx, mFrame, mAppearance, mBackgroundRect, drawing);
+}
+
+bool nsDisplayThemedBackground::IsWindowActive()
+{
+ EventStates docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+}
+
+void nsDisplayThemedBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ const nsDisplayThemedBackgroundGeometry* geometry = static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry);
+
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRect positioningArea = GetPositioningArea();
+ if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) {
+ // Invalidate everything (both old and new painting areas).
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ return;
+ }
+ if (!bounds.IsEqualInterior(geometry->mBounds)) {
+ // Positioning area is unchanged, so invalidate just the change in the
+ // painting area.
+ aInvalidRegion->Xor(bounds, geometry->mBounds);
+ }
+ nsITheme* theme = mFrame->PresContext()->GetTheme();
+ if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) &&
+ IsWindowActive() != geometry->mWindowIsActive) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ }
+}
+
+nsRect
+nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = true;
+ return mBounds;
+}
+
+nsRect
+nsDisplayThemedBackground::GetBoundsInternal() {
+ nsPresContext* presContext = mFrame->PresContext();
+
+ nsRect r = mBackgroundRect - ToReferenceFrame();
+ presContext->GetTheme()->
+ GetWidgetOverflow(presContext->DeviceContext(), mFrame,
+ mFrame->StyleDisplay()->mAppearance, &r);
+ return r + ToReferenceFrame();
+}
+
+void
+nsDisplayImageContainer::ConfigureLayer(ImageLayer* aLayer,
+ const ContainerLayerParameters& aParameters)
+{
+ aLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(mFrame));
+
+ nsCOMPtr<imgIContainer> image = GetImage();
+ MOZ_ASSERT(image);
+ int32_t imageWidth;
+ int32_t imageHeight;
+ image->GetWidth(&imageWidth);
+ image->GetHeight(&imageHeight);
+ NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
+
+ if (imageWidth > 0 && imageHeight > 0) {
+ // We're actually using the ImageContainer. Let our frame know that it
+ // should consider itself to have painted successfully.
+ nsDisplayBackgroundGeometry::UpdateDrawResult(this,
+ image::DrawResult::SUCCESS);
+ }
+
+ // XXX(seth): Right now we ignore aParameters.Scale() and
+ // aParameters.Offset(), because FrameLayerBuilder already applies
+ // aParameters.Scale() via the layer's post-transform, and
+ // aParameters.Offset() is always zero.
+ MOZ_ASSERT(aParameters.Offset() == LayerIntPoint(0,0));
+
+ // It's possible (for example, due to downscale-during-decode) that the
+ // ImageContainer this ImageLayer is holding has a different size from the
+ // intrinsic size of the image. For this reason we compute the transform using
+ // the ImageContainer's size rather than the image's intrinsic size.
+ // XXX(seth): In reality, since the size of the ImageContainer may change
+ // asynchronously, this is not enough. Bug 1183378 will provide a more
+ // complete fix, but this solution is safe in more cases than simply relying
+ // on the intrinsic size.
+ IntSize containerSize = aLayer->GetContainer()
+ ? aLayer->GetContainer()->GetCurrentSize()
+ : IntSize(imageWidth, imageHeight);
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
+
+ const LayoutDevicePoint p = destRect.TopLeft();
+ Matrix transform = Matrix::Translation(p.x, p.y);
+ transform.PreScale(destRect.width / containerSize.width,
+ destRect.height / containerSize.height);
+ aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
+}
+
+already_AddRefed<ImageContainer>
+nsDisplayImageContainer::GetContainer(LayerManager* aManager,
+ nsDisplayListBuilder *aBuilder)
+{
+ nsCOMPtr<imgIContainer> image = GetImage();
+ if (!image) {
+ MOZ_ASSERT_UNREACHABLE("Must call CanOptimizeToImage() and get true "
+ "before calling GetContainer()");
+ return nullptr;
+ }
+
+ uint32_t flags = aBuilder->ShouldSyncDecodeImages()
+ ? imgIContainer::FLAG_SYNC_DECODE
+ : imgIContainer::FLAG_NONE;
+
+ return image->GetImageContainer(aManager, flags);
+}
+
+bool
+nsDisplayImageContainer::CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder)
+{
+ uint32_t flags = aBuilder->ShouldSyncDecodeImages()
+ ? imgIContainer::FLAG_SYNC_DECODE
+ : imgIContainer::FLAG_NONE;
+
+ nsCOMPtr<imgIContainer> image = GetImage();
+ if (!image) {
+ return false;
+ }
+
+ if (!image->IsImageContainerAvailable(aManager, flags)) {
+ return false;
+ }
+
+ int32_t imageWidth;
+ int32_t imageHeight;
+ image->GetWidth(&imageWidth);
+ image->GetHeight(&imageHeight);
+
+ if (imageWidth == 0 || imageHeight == 0) {
+ NS_ASSERTION(false, "invalid image size");
+ return false;
+ }
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
+
+ // Calculate the scaling factor for the frame.
+ const gfxSize scale = gfxSize(destRect.width / imageWidth,
+ destRect.height / imageHeight);
+
+ if (scale.width < 0.34 || scale.height < 0.34) {
+ // This would look awful as long as we can't use high-quality downscaling
+ // for image layers (bug 803703), so don't turn this into an image layer.
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsDisplayBackgroundColor::ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip)
+{
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mColor.a = mColor.a * aOpacity;
+ if (aClip) {
+ IntersectClip(aBuilder, *aClip);
+ }
+}
+
+bool
+nsDisplayBackgroundColor::CanApplyOpacity() const
+{
+ return true;
+}
+
+void
+nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ if (mColor == Color()) {
+ return;
+ }
+
+#if 0
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this
+ // results in a precision induced rounding issue that makes the rect one
+ // pixel shorter in rare cases. Disabled in favor of the old code for now.
+ // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to
+ // reproduce the bug.
+ //
+ // TODO:
+ // This new path does not include support for background-clip:text; need to
+ // be fixed if/when we switch to this new code path.
+
+ DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
+
+ Rect rect = NSRectToSnappedRect(mBackgroundRect,
+ mFrame->PresContext()->AppUnitsPerDevPixel(),
+ aDrawTarget);
+ ColorPattern color(ToDeviceColor(mColor));
+ aDrawTarget.FillRect(rect, color);
+#else
+ gfxContext* ctx = aCtx->ThebesContext();
+ gfxRect bounds =
+ nsLayoutUtils::RectToGfxRect(mBackgroundRect,
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ uint8_t clip = mBackgroundStyle->mImage.mLayers[0].mClip;
+ if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) {
+ return;
+ }
+
+ ctx->SetColor(mColor);
+ ctx->Rectangle(bounds, true);
+ ctx->Fill();
+ ctx->PopGroupAndBlend();
+ return;
+ }
+
+ ctx->SetColor(mColor);
+ ctx->NewPath();
+ ctx->Rectangle(bounds, true);
+ ctx->Fill();
+#endif
+}
+
+nsRegion
+nsDisplayBackgroundColor::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap)
+{
+ *aSnap = false;
+
+ if (mColor.a != 1) {
+ return nsRegion();
+ }
+
+ if (!mBackgroundStyle)
+ return nsRegion();
+
+
+ const nsStyleImageLayers::Layer& bottomLayer = mBackgroundStyle->BottomLayer();
+ if (bottomLayer.mClip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+ return nsRegion();
+ }
+
+ *aSnap = true;
+ return nsDisplayBackgroundImage::GetInsideClipRegion(this, bottomLayer.mClip,
+ mBackgroundRect, mBackgroundRect);
+}
+
+Maybe<nscolor>
+nsDisplayBackgroundColor::IsUniform(nsDisplayListBuilder* aBuilder)
+{
+ return Some(mColor.ToABGR());
+}
+
+void
+nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ // aRect doesn't intersect our border-radius curve.
+ return;
+ }
+
+ aOutFrames->AppendElement(mFrame);
+}
+
+void
+nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (rgba " << mColor.r << "," << mColor.g << ","
+ << mColor.b << "," << mColor.a << ")";
+}
+
+already_AddRefed<Layer>
+nsDisplayClearBackground::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ RefPtr<ColorLayer> layer = static_cast<ColorLayer*>
+ (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
+ if (!layer) {
+ layer = aManager->CreateColorLayer();
+ if (!layer)
+ return nullptr;
+ }
+ layer->SetColor(Color());
+ layer->SetMixBlendMode(gfx::CompositionOp::OP_SOURCE);
+
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ layer->SetBounds(bounds.ToNearestPixels(appUnitsPerDevPixel)); // XXX Do we need to respect the parent layer's scale here?
+
+ return layer.forget();
+}
+
+nsRect
+nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+void
+nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ // TODO join outlines together
+ nsPoint offset = ToReferenceFrame();
+ nsCSSRendering::PaintOutline(mFrame->PresContext(), *aCtx, mFrame,
+ mVisibleRect,
+ nsRect(offset, mFrame->GetSize()),
+ mFrame->StyleContext());
+}
+
+bool
+nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect)
+{
+ const nsStyleOutline* outline = mFrame->StyleOutline();
+ nsRect borderBox(ToReferenceFrame(), mFrame->GetSize());
+ if (borderBox.Contains(aRect) &&
+ !nsLayoutUtils::HasNonZeroCorner(outline->mOutlineRadius)) {
+ if (outline->mOutlineOffset >= 0) {
+ // aRect is entirely inside the border-rect, and the outline isn't
+ // rendered inside the border-rect, so the outline is not visible.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
+ // aRect doesn't intersect our border-radius curve.
+ return;
+ }
+
+ aOutFrames->AppendElement(mFrame);
+}
+
+void
+nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+{
+ NS_ASSERTION(aBuilder->FindReferenceFrameFor(aFrame) == aBuilder->FindReferenceFrameFor(mFrame),
+ "Reference frame mismatch");
+ if (aBuilder->IsInsidePointerEventsNoneDoc()) {
+ // Somewhere up the parent document chain is a subdocument with pointer-
+ // events:none set on it.
+ return;
+ }
+ if (!aFrame->GetParent()) {
+ MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::viewportFrame);
+ // Viewport frames are never event targets, other frames, like canvas frames,
+ // are the event targets for any regions viewport frames may cover.
+ return;
+ }
+
+ uint8_t pointerEvents =
+ aFrame->StyleUserInterface()->GetEffectivePointerEvents(aFrame);
+ if (pointerEvents == NS_STYLE_POINTER_EVENTS_NONE) {
+ return;
+ }
+ bool simpleRegions = aFrame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
+ if (!simpleRegions) {
+ if (!aFrame->StyleVisibility()->IsVisible()) {
+ return;
+ }
+ }
+ // XXX handle other pointerEvents values for SVG
+
+ // XXX Do something clever here for the common case where the border box
+ // is obviously entirely inside mHitRegion.
+ nsRect borderBox;
+ if (nsLayoutUtils::GetScrollableFrameFor(aFrame)) {
+ // If the frame is content of a scrollframe, then we need to pick up the
+ // area corresponding to the overflow rect as well. Otherwise the parts of
+ // the overflow that are not occupied by descendants get skipped and the
+ // APZ code sends touch events to the content underneath instead.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
+ borderBox = aFrame->GetScrollableOverflowRect();
+ } else {
+ borderBox = nsRect(nsPoint(0, 0), aFrame->GetSize());
+ }
+ borderBox += aBuilder->ToReferenceFrame(aFrame);
+
+ bool borderBoxHasRoundedCorners = false;
+ if (!simpleRegions) {
+ if (nsLayoutUtils::HasNonZeroCorner(aFrame->StyleBorder()->mBorderRadius)) {
+ borderBoxHasRoundedCorners = true;
+ } else {
+ aFrame->AddStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
+ }
+ }
+
+ const DisplayItemClip* clip = aBuilder->ClipState().GetCurrentCombinedClip(aBuilder);
+ if (clip) {
+ borderBox = clip->ApplyNonRoundedIntersection(borderBox);
+ if (clip->GetRoundedRectCount() > 0) {
+ borderBoxHasRoundedCorners = true;
+ }
+ }
+
+ if (borderBoxHasRoundedCorners ||
+ (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ mMaybeHitRegion.Or(mMaybeHitRegion, borderBox);
+
+ // Avoid quadratic performance as a result of the region growing to include
+ // an arbitrarily large number of rects, which can happen on some pages.
+ mMaybeHitRegion.SimplifyOutward(8);
+ } else {
+ mHitRegion.Or(mHitRegion, borderBox);
+ }
+
+ if (aBuilder->IsBuildingNonLayerizedScrollbar() ||
+ aBuilder->GetAncestorHasApzAwareEventHandler())
+ {
+ // Scrollbars may be painted into a layer below the actual layer they will
+ // scroll, and therefore wheel events may be dispatched to the outer frame
+ // instead of the intended scrollframe. To address this, we force a d-t-c
+ // region on scrollbar frames that won't be placed in their own layer. See
+ // bug 1213324 for details.
+ mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
+ } else if (aFrame->GetType() == nsGkAtoms::objectFrame) {
+ // If the frame is a plugin frame and wants to handle wheel events as
+ // default action, we should add the frame to dispatch-to-content region.
+ nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
+ if (pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
+ mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
+ }
+ }
+
+ // Touch action region
+
+ nsIFrame* touchActionFrame = aFrame;
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(aFrame);
+ if (scrollFrame) {
+ touchActionFrame = do_QueryFrame(scrollFrame);
+ }
+ uint32_t touchAction = nsLayoutUtils::GetTouchActionFromFrame(touchActionFrame);
+ if (touchAction != NS_STYLE_TOUCH_ACTION_AUTO) {
+ // If this frame has touch-action areas, and there were already
+ // touch-action areas from some other element on this same event regions,
+ // then all we know is that there are multiple elements with touch-action
+ // properties. In particular, we don't know what the relationship is
+ // between those elements in terms of DOM ancestry, and so we don't know
+ // how to combine the regions properly. Instead, we just add all the areas
+ // to the dispatch-to-content region, so that the APZ knows to check with
+ // the main thread. XXX we need to come up with a better way to do this,
+ // see bug 1287829.
+ bool alreadyHadRegions = !mNoActionRegion.IsEmpty() ||
+ !mHorizontalPanRegion.IsEmpty() ||
+ !mVerticalPanRegion.IsEmpty();
+ if (touchAction & NS_STYLE_TOUCH_ACTION_NONE) {
+ mNoActionRegion.OrWith(borderBox);
+ } else {
+ if ((touchAction & NS_STYLE_TOUCH_ACTION_PAN_X)) {
+ mHorizontalPanRegion.OrWith(borderBox);
+ }
+ if ((touchAction & NS_STYLE_TOUCH_ACTION_PAN_Y)) {
+ mVerticalPanRegion.OrWith(borderBox);
+ }
+ }
+ if (alreadyHadRegions) {
+ mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
+ }
+ }
+}
+
+void
+nsDisplayLayerEventRegions::AddInactiveScrollPort(const nsRect& aRect)
+{
+ mHitRegion.Or(mHitRegion, aRect);
+ mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, aRect);
+}
+
+bool
+nsDisplayLayerEventRegions::IsEmpty() const
+{
+ // If the hit region and maybe-hit region are empty, then the rest
+ // must be empty too.
+ if (mHitRegion.IsEmpty() && mMaybeHitRegion.IsEmpty()) {
+ MOZ_ASSERT(mDispatchToContentHitRegion.IsEmpty());
+ MOZ_ASSERT(mNoActionRegion.IsEmpty());
+ MOZ_ASSERT(mHorizontalPanRegion.IsEmpty());
+ MOZ_ASSERT(mVerticalPanRegion.IsEmpty());
+ return true;
+ }
+ return false;
+}
+
+nsRegion
+nsDisplayLayerEventRegions::CombinedTouchActionRegion()
+{
+ nsRegion result;
+ result.Or(mHorizontalPanRegion, mVerticalPanRegion);
+ result.OrWith(mNoActionRegion);
+ return result;
+}
+
+int32_t
+nsDisplayLayerEventRegions::ZIndex() const
+{
+ return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
+}
+
+void
+nsDisplayLayerEventRegions::SetOverrideZIndex(int32_t aZIndex)
+{
+ mOverrideZIndex = Some(aZIndex);
+}
+
+void
+nsDisplayLayerEventRegions::WriteDebugInfo(std::stringstream& aStream)
+{
+ if (!mHitRegion.IsEmpty()) {
+ AppendToString(aStream, mHitRegion, " (hitRegion ", ")");
+ }
+ if (!mMaybeHitRegion.IsEmpty()) {
+ AppendToString(aStream, mMaybeHitRegion, " (maybeHitRegion ", ")");
+ }
+ if (!mDispatchToContentHitRegion.IsEmpty()) {
+ AppendToString(aStream, mDispatchToContentHitRegion, " (dispatchToContentRegion ", ")");
+ }
+ if (!mNoActionRegion.IsEmpty()) {
+ AppendToString(aStream, mNoActionRegion, " (noActionRegion ", ")");
+ }
+ if (!mHorizontalPanRegion.IsEmpty()) {
+ AppendToString(aStream, mHorizontalPanRegion, " (horizPanRegion ", ")");
+ }
+ if (!mVerticalPanRegion.IsEmpty()) {
+ AppendToString(aStream, mVerticalPanRegion, " (vertPanRegion ", ")");
+ }
+}
+
+nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aCaretFrame)
+ : nsDisplayItem(aBuilder, aCaretFrame)
+ , mCaret(aBuilder->GetCaret())
+ , mBounds(aBuilder->GetCaretRect() + ToReferenceFrame())
+{
+ MOZ_COUNT_CTOR(nsDisplayCaret);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayCaret::~nsDisplayCaret()
+{
+ MOZ_COUNT_DTOR(nsDisplayCaret);
+}
+#endif
+
+nsRect
+nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = true;
+ // The caret returns a rect in the coordinates of mFrame.
+ return mBounds;
+}
+
+void
+nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ // Note: Because we exist, we know that the caret is visible, so we don't
+ // need to check for the caret's visibility.
+ mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame());
+}
+
+nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+{
+ MOZ_COUNT_CTOR(nsDisplayBorder);
+
+ mBounds = CalculateBounds(*mFrame->StyleBorder());
+}
+
+bool
+nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect)
+{
+ nsRect paddingRect = mFrame->GetPaddingRect() - mFrame->GetPosition() +
+ ToReferenceFrame();
+ const nsStyleBorder *styleBorder;
+ if (paddingRect.Contains(aRect) &&
+ !(styleBorder = mFrame->StyleBorder())->IsBorderImageLoaded() &&
+ !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) {
+ // aRect is entirely inside the content rect, and no part
+ // of the border is rendered inside the content rect, so we are not
+ // visible
+ // Skip this if there's a border-image (which draws a background
+ // too) or if there is a border-radius (which makes the border draw
+ // further in).
+ return true;
+ }
+
+ return false;
+}
+
+nsDisplayItemGeometry*
+nsDisplayBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayBorderGeometry(this, aBuilder);
+}
+
+void
+nsDisplayBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ const nsDisplayBorderGeometry* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry);
+ bool snap;
+
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mContentRect.IsEqualInterior(GetContentRect())) {
+ // We can probably get away with only invalidating the difference
+ // between the border and padding rects, but the XUL ui at least
+ // is apparently painting a background with this?
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+}
+
+void
+nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ image::DrawResult result =
+ nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
+ mVisibleRect,
+ nsRect(offset, mFrame->GetSize()),
+ mFrame->StyleContext(),
+ flags,
+ mFrame->GetSkipSides());
+
+ nsDisplayBorderGeometry::UpdateDrawResult(this, result);
+}
+
+nsRect
+nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = true;
+ return mBounds;
+}
+
+nsRect
+nsDisplayBorder::CalculateBounds(const nsStyleBorder& aStyleBorder)
+{
+ nsRect borderBounds(ToReferenceFrame(), mFrame->GetSize());
+ if (aStyleBorder.IsBorderImageLoaded()) {
+ borderBounds.Inflate(aStyleBorder.GetImageOutset());
+ return borderBounds;
+ } else {
+ nsMargin border = aStyleBorder.GetComputedBorder();
+ nsRect result;
+ if (border.top > 0) {
+ result = nsRect(borderBounds.X(), borderBounds.Y(), borderBounds.Width(), border.top);
+ }
+ if (border.right > 0) {
+ result.UnionRect(result, nsRect(borderBounds.XMost() - border.right, borderBounds.Y(), border.right, borderBounds.Height()));
+ }
+ if (border.bottom > 0) {
+ result.UnionRect(result, nsRect(borderBounds.X(), borderBounds.YMost() - border.bottom, borderBounds.Width(), border.bottom));
+ }
+ if (border.left > 0) {
+ result.UnionRect(result, nsRect(borderBounds.X(), borderBounds.Y(), border.left, borderBounds.Height()));
+ }
+
+ nscoord radii[8];
+ if (mFrame->GetBorderRadii(radii)) {
+ if (border.left > 0 || border.top > 0) {
+ nsSize cornerSize(radii[NS_CORNER_TOP_LEFT_X], radii[NS_CORNER_TOP_LEFT_Y]);
+ result.UnionRect(result, nsRect(borderBounds.TopLeft(), cornerSize));
+ }
+ if (border.top > 0 || border.right > 0) {
+ nsSize cornerSize(radii[NS_CORNER_TOP_RIGHT_X], radii[NS_CORNER_TOP_RIGHT_Y]);
+ result.UnionRect(result, nsRect(borderBounds.TopRight() - nsPoint(cornerSize.width, 0), cornerSize));
+ }
+ if (border.right > 0 || border.bottom > 0) {
+ nsSize cornerSize(radii[NS_CORNER_BOTTOM_RIGHT_X], radii[NS_CORNER_BOTTOM_RIGHT_Y]);
+ result.UnionRect(result, nsRect(borderBounds.BottomRight() - nsPoint(cornerSize.width, cornerSize.height), cornerSize));
+ }
+ if (border.bottom > 0 || border.left > 0) {
+ nsSize cornerSize(radii[NS_CORNER_BOTTOM_LEFT_X], radii[NS_CORNER_BOTTOM_LEFT_Y]);
+ result.UnionRect(result, nsRect(borderBounds.BottomLeft() - nsPoint(0, cornerSize.height), cornerSize));
+ }
+ }
+
+ return result;
+ }
+}
+
+// Given a region, compute a conservative approximation to it as a list
+// of rectangles that aren't vertically adjacent (i.e., vertically
+// adjacent or overlapping rectangles are combined).
+// Right now this is only approximate, some vertically overlapping rectangles
+// aren't guaranteed to be combined.
+static void
+ComputeDisjointRectangles(const nsRegion& aRegion,
+ nsTArray<nsRect>* aRects) {
+ nscoord accumulationMargin = nsPresContext::CSSPixelsToAppUnits(25);
+ nsRect accumulated;
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& r = iter.Get();
+ if (accumulated.IsEmpty()) {
+ accumulated = r;
+ continue;
+ }
+
+ if (accumulated.YMost() >= r.y - accumulationMargin) {
+ accumulated.UnionRect(accumulated, r);
+ } else {
+ aRects->AppendElement(accumulated);
+ accumulated = r;
+ }
+ }
+
+ // Finish the in-flight rectangle, if there is one.
+ if (!accumulated.IsEmpty()) {
+ aRects->AppendElement(accumulated);
+ }
+}
+
+void
+nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
+ nsPresContext* presContext = mFrame->PresContext();
+ AutoTArray<nsRect,10> rects;
+ ComputeDisjointRectangles(mVisibleRegion, &rects);
+
+ PROFILER_LABEL("nsDisplayBoxShadowOuter", "Paint",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame,
+ borderRect, rects[i], mOpacity);
+ }
+}
+
+nsRect
+nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mBounds;
+}
+
+nsRect
+nsDisplayBoxShadowOuter::GetBoundsInternal() {
+ return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) +
+ ToReferenceFrame();
+}
+
+bool
+nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect)
+{
+ nsPoint origin = ToReferenceFrame();
+ nsRect frameRect(origin, mFrame->GetSize());
+ if (!frameRect.Contains(aRect))
+ return false;
+
+ // the visible region is entirely inside the border-rect, and box shadows
+ // never render within the border-rect (unless there's a border radius).
+ nscoord twipsRadii[8];
+ bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii);
+ if (!hasBorderRadii)
+ return true;
+
+ return RoundedRectContainsRect(frameRect, twipsRadii, aRect);
+}
+
+bool
+nsDisplayBoxShadowOuter::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (!nsDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ // Store the actual visible region
+ mVisibleRegion.And(*aVisibleRegion, mVisibleRect);
+ return true;
+}
+
+void
+nsDisplayBoxShadowOuter::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ const nsDisplayBoxShadowOuterGeometry* geometry =
+ static_cast<const nsDisplayBoxShadowOuterGeometry*>(aGeometry);
+ bool snap;
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
+ mOpacity != geometry->mOpacity) {
+ nsRegion oldShadow, newShadow;
+ nscoord dontCare[8];
+ bool hasBorderRadius = mFrame->GetBorderRadii(dontCare);
+ if (hasBorderRadius) {
+ // If we have rounded corners then we need to invalidate the frame area
+ // too since we paint into it.
+ oldShadow = geometry->mBounds;
+ newShadow = GetBounds(aBuilder, &snap);
+ } else {
+ oldShadow.Sub(geometry->mBounds, geometry->mBorderRect);
+ newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect());
+ }
+ aInvalidRegion->Or(oldShadow, newShadow);
+ }
+}
+
+
+void
+nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ nsPoint offset = ToReferenceFrame();
+ nsRect borderRect = nsRect(offset, mFrame->GetSize());
+ nsPresContext* presContext = mFrame->PresContext();
+ AutoTArray<nsRect,10> rects;
+ ComputeDisjointRectangles(mVisibleRegion, &rects);
+
+ PROFILER_LABEL("nsDisplayBoxShadowInner", "Paint",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ gfxContext* gfx = aCtx->ThebesContext();
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ for (uint32_t i = 0; i < rects.Length(); ++i) {
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(rects[i], appUnitsPerDevPixel, *drawTarget));
+ nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect);
+ gfx->Restore();
+ }
+}
+
+bool
+nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ if (!nsDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) {
+ return false;
+ }
+
+ // Store the actual visible region
+ mVisibleRegion.And(*aVisibleRegion, mVisibleRect);
+ return true;
+}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList,
+ aBuilder->ClipState().GetCurrentInnermostScrollClip())
+{}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip)
+ : nsDisplayItem(aBuilder, aFrame, aScrollClip)
+ , mOverrideZIndex(0)
+ , mHasZIndexOverride(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseVisibleRect = mVisibleRect;
+
+ mList.AppendToTop(aList);
+ UpdateBounds(aBuilder);
+
+ if (!aFrame || !aFrame->IsTransformed()) {
+ return;
+ }
+
+ // If we're a transformed frame, then we need to find out if we're inside
+ // the nsDisplayTransform or outside of it. Frames inside the transform
+ // need mReferenceFrame == mFrame, outside needs the next ancestor
+ // reference frame.
+ // If we're inside the transform, then the nsDisplayItem constructor
+ // will have done the right thing.
+ // If we're outside the transform, then we should have only one child
+ // (since nsDisplayTransform wraps all actual content), and that child
+ // will have the correct reference frame set (since nsDisplayTransform
+ // handles this explictly).
+ nsDisplayItem *i = mList.GetBottom();
+ if (i && (!i->GetAbove() || i->GetType() == TYPE_TRANSFORM) &&
+ i->Frame() == mFrame) {
+ mReferenceFrame = i->ReferenceFrame();
+ mToReferenceFrame = i->ToReferenceFrame();
+ }
+ mVisibleRect = aBuilder->GetDirtyRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+}
+
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayItem* aItem)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mOverrideZIndex(0)
+ , mHasZIndexOverride(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+
+ mBaseVisibleRect = mVisibleRect;
+
+ mList.AppendToTop(aItem);
+ UpdateBounds(aBuilder);
+
+ if (!aFrame || !aFrame->IsTransformed()) {
+ return;
+ }
+
+ // See the previous nsDisplayWrapList constructor
+ if (aItem->Frame() == aFrame) {
+ mReferenceFrame = aItem->ReferenceFrame();
+ mToReferenceFrame = aItem->ToReferenceFrame();
+ }
+ mVisibleRect = aBuilder->GetDirtyRect() +
+ aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+}
+
+nsDisplayWrapList::~nsDisplayWrapList() {
+ mList.DeleteAll();
+
+ MOZ_COUNT_DTOR(nsDisplayWrapList);
+}
+
+void
+nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
+ mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+}
+
+nsRect
+nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mBounds;
+}
+
+bool
+nsDisplayWrapList::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Convert the passed in visible region to our appunits.
+ nsRegion visibleRegion;
+ // mVisibleRect has been clipped to GetClippedBounds
+ visibleRegion.And(*aVisibleRegion, mVisibleRect);
+ nsRegion originalVisibleRegion = visibleRegion;
+
+ bool retval =
+ mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion, mVisibleRect);
+
+ nsRegion removed;
+ // removed = originalVisibleRegion - visibleRegion
+ removed.Sub(originalVisibleRegion, visibleRegion);
+ // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications
+ // SubtractFromVisibleRegion does)
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+
+ return retval;
+}
+
+nsRegion
+nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) {
+ *aSnap = false;
+ nsRegion result;
+ if (mList.IsOpaque()) {
+ // Everything within GetBounds that's visible is opaque.
+ result = GetBounds(aBuilder, aSnap);
+ }
+ return result;
+}
+
+Maybe<nscolor>
+nsDisplayWrapList::IsUniform(nsDisplayListBuilder* aBuilder) {
+ // We could try to do something but let's conservatively just return Nothing.
+ return Nothing();
+}
+
+void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ NS_ERROR("nsDisplayWrapList should have been flattened away for painting");
+}
+
+/**
+ * Returns true if all descendant display items can be placed in the same
+ * PaintedLayer --- GetLayerState returns LAYER_INACTIVE or LAYER_NONE,
+ * and they all have the expected animated geometry root.
+ */
+static LayerState
+RequiredLayerStateForChildren(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters,
+ const nsDisplayList& aList,
+ AnimatedGeometryRoot* aExpectedAnimatedGeometryRootForChildren)
+{
+ LayerState result = LAYER_INACTIVE;
+ for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
+ if (result == LAYER_INACTIVE &&
+ i->GetAnimatedGeometryRoot() != aExpectedAnimatedGeometryRootForChildren) {
+ result = LAYER_ACTIVE;
+ }
+
+ LayerState state = i->GetLayerState(aBuilder, aManager, aParameters);
+ if (state == LAYER_ACTIVE && i->GetType() == nsDisplayItem::TYPE_BLEND_MODE) {
+ // nsDisplayBlendMode always returns LAYER_ACTIVE to ensure that the
+ // blending operation happens in the intermediate surface of its parent
+ // display item (usually an nsDisplayBlendContainer). But this does not
+ // mean that it needs all its ancestor display items to become active.
+ // So we ignore its layer state and look at its children instead.
+ state = RequiredLayerStateForChildren(aBuilder, aManager, aParameters,
+ *i->GetSameCoordinateSystemChildren(), i->GetAnimatedGeometryRoot());
+ }
+ if ((state == LAYER_ACTIVE || state == LAYER_ACTIVE_FORCE) &&
+ state > result) {
+ result = state;
+ }
+ if (state == LAYER_ACTIVE_EMPTY && state > result) {
+ result = LAYER_ACTIVE_FORCE;
+ }
+ if (state == LAYER_NONE) {
+ nsDisplayList* list = i->GetSameCoordinateSystemChildren();
+ if (list) {
+ LayerState childState =
+ RequiredLayerStateForChildren(aBuilder, aManager, aParameters, *list,
+ aExpectedAnimatedGeometryRootForChildren);
+ if (childState > result) {
+ result = childState;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+nsRect nsDisplayWrapList::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
+{
+ nsRect bounds;
+ for (nsDisplayItem* i = mList.GetBottom(); i; i = i->GetAbove()) {
+ bounds.UnionRect(bounds, i->GetComponentAlphaBounds(aBuilder));
+ }
+ return bounds;
+}
+
+void
+nsDisplayWrapList::SetVisibleRect(const nsRect& aRect)
+{
+ mVisibleRect = aRect;
+}
+
+void
+nsDisplayWrapList::SetReferenceFrame(const nsIFrame* aFrame)
+{
+ mReferenceFrame = aFrame;
+ mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
+}
+
+static nsresult
+WrapDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
+ if (!aList->GetTop())
+ return NS_OK;
+ nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList);
+ if (!item)
+ return NS_ERROR_OUT_OF_MEMORY;
+ // aList was emptied
+ aList->AppendToTop(item);
+ return NS_OK;
+}
+
+static nsresult
+WrapEachDisplayItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
+ nsDisplayList newList;
+ nsDisplayItem* item;
+ while ((item = aList->RemoveBottom())) {
+ item = aWrapper->WrapItem(aBuilder, item);
+ if (!item)
+ return NS_ERROR_OUT_OF_MEMORY;
+ newList.AppendToTop(item);
+ }
+ // aList was emptied
+ aList->AppendToTop(&newList);
+ return NS_OK;
+}
+
+nsresult nsDisplayWrapper::WrapLists(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsDisplayListSet& aIn, const nsDisplayListSet& aOut)
+{
+ nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (&aOut == &aIn)
+ return NS_OK;
+ aOut.BorderBackground()->AppendToTop(aIn.BorderBackground());
+ aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds());
+ aOut.Floats()->AppendToTop(aIn.Floats());
+ aOut.Content()->AppendToTop(aIn.Content());
+ aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants());
+ aOut.Outlines()->AppendToTop(aIn.Outlines());
+ return NS_OK;
+}
+
+nsresult nsDisplayWrapper::WrapListsInPlace(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsDisplayListSet& aLists)
+{
+ nsresult rv;
+ if (WrapBorderBackground()) {
+ // Our border-backgrounds are in-flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Our block border-backgrounds are in-flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The floats are not in flow
+ rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Our child content is in flow
+ rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The positioned descendants may not be in-flow
+ rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The outlines may not be in-flow
+ return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this);
+}
+
+nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip,
+ bool aForEventsAndPluginsOnly)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+ , mOpacity(aFrame->StyleEffects()->mOpacity)
+ , mForEventsAndPluginsOnly(aForEventsAndPluginsOnly)
+{
+ MOZ_COUNT_CTOR(nsDisplayOpacity);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayOpacity::~nsDisplayOpacity() {
+ MOZ_COUNT_DTOR(nsDisplayOpacity);
+}
+#endif
+
+nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) {
+ *aSnap = false;
+ // The only time where mOpacity == 1.0 should be when we have will-change.
+ // We could report this as opaque then but when the will-change value starts
+ // animating the element would become non opaque and could cause repaints.
+ return nsRegion();
+}
+
+// nsDisplayOpacity uses layers for rendering
+already_AddRefed<Layer>
+nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ContainerLayerParameters params = aContainerParameters;
+ params.mForEventsAndPluginsOnly = mForEventsAndPluginsOnly;
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ params, nullptr,
+ FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+ if (!container)
+ return nullptr;
+
+ container->SetOpacity(mOpacity);
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder,
+ this, mFrame,
+ eCSSProperty_opacity);
+ return container.forget();
+}
+
+/**
+ * This doesn't take into account layer scaling --- the layer may be
+ * rendered at a higher (or lower) resolution, affecting the retained layer
+ * size --- but this should be good enough.
+ */
+static bool
+IsItemTooSmallForActiveLayer(nsIFrame* aFrame)
+{
+ nsIntRect visibleDevPixels = aFrame->GetVisualOverflowRectRelativeToSelf().ToOutsidePixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+ static const int MIN_ACTIVE_LAYER_SIZE_DEV_PIXELS = 16;
+ return visibleDevPixels.Size() <
+ nsIntSize(MIN_ACTIVE_LAYER_SIZE_DEV_PIXELS, MIN_ACTIVE_LAYER_SIZE_DEV_PIXELS);
+}
+
+static void
+SetAnimationPerformanceWarningForTooSmallItem(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty)
+{
+ // We use ToNearestPixels() here since ToOutsidePixels causes some sort of
+ // errors. See https://bugzilla.mozilla.org/show_bug.cgi?id=1258904#c19
+ nsIntRect visibleDevPixels = aFrame->GetVisualOverflowRectRelativeToSelf().ToNearestPixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+
+ // Set performance warning only if the visible dev pixels is not empty
+ // because dev pixels is empty if the frame has 'preserve-3d' style.
+ if (visibleDevPixels.IsEmpty()) {
+ return;
+ }
+
+ EffectCompositor::SetPerformanceWarning(aFrame, aProperty,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::ContentTooSmall,
+ { visibleDevPixels.Width(), visibleDevPixels.Height() }));
+}
+
+/* static */ bool
+nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+{
+ if (ActiveLayerTracker::IsStyleAnimated(aBuilder, aFrame,
+ eCSSProperty_opacity) ||
+ EffectCompositor::HasAnimationsForCompositor(aFrame,
+ eCSSProperty_opacity)) {
+ if (!IsItemTooSmallForActiveLayer(aFrame)) {
+ return true;
+ }
+ SetAnimationPerformanceWarningForTooSmallItem(aFrame, eCSSProperty_opacity);
+ }
+ return false;
+}
+
+void
+nsDisplayOpacity::ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip)
+{
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mOpacity = mOpacity * aOpacity;
+ if (aClip) {
+ IntersectClip(aBuilder, *aClip);
+ }
+}
+
+bool
+nsDisplayOpacity::CanApplyOpacity() const
+{
+ return true;
+}
+
+bool
+nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
+{
+ if (NeedsActiveLayer(aBuilder, mFrame) || mOpacity == 0.0) {
+ // If our opacity is zero then we'll discard all descendant display items
+ // except for layer event regions, so there's no point in doing this
+ // optimization (and if we do do it, then invalidations of those descendants
+ // might trigger repainting).
+ return false;
+ }
+
+ nsDisplayItem* child = mList.GetBottom();
+ // Only try folding our opacity down if we have at most three children
+ // that don't overlap and can all apply the opacity to themselves.
+ if (!child) {
+ return false;
+ }
+ struct {
+ nsDisplayItem* item;
+ nsRect bounds;
+ } children[3];
+ bool snap;
+ uint32_t numChildren = 0;
+ for (; numChildren < ArrayLength(children) && child; numChildren++, child = child->GetAbove()) {
+ if (child->GetType() == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+ numChildren--;
+ continue;
+ }
+ if (!child->CanApplyOpacity()) {
+ return false;
+ }
+ children[numChildren].item = child;
+ children[numChildren].bounds = child->GetBounds(aBuilder, &snap);
+ }
+ if (child) {
+ // we have a fourth (or more) child
+ return false;
+ }
+
+ for (uint32_t i = 0; i < numChildren; i++) {
+ for (uint32_t j = i+1; j < numChildren; j++) {
+ if (children[i].bounds.Intersects(children[j].bounds)) {
+ return false;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < numChildren; i++) {
+ children[i].item->ApplyOpacity(aBuilder, mOpacity, mClip);
+ }
+ return true;
+}
+
+nsDisplayItem::LayerState
+nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ // If we only created this item so that we'd get correct nsDisplayEventRegions for child
+ // frames, then force us to inactive to avoid unnecessary layerization changes for content
+ // that won't ever be painted.
+ if (mForEventsAndPluginsOnly) {
+ MOZ_ASSERT(mOpacity == 0);
+ return LAYER_INACTIVE;
+ }
+
+ if (NeedsActiveLayer(aBuilder, mFrame)) {
+ // Returns LAYER_ACTIVE_FORCE to avoid flatterning the layer for async
+ // animations.
+ return LAYER_ACTIVE_FORCE;
+ }
+
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot());
+}
+
+bool
+nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Our children are translucent so we should not allow them to subtract
+ // area from aVisibleRegion. We do need to find out what is visible under
+ // our children in the temporary compositing buffer, because if our children
+ // paint our entire bounds opaquely then we don't need an alpha channel in
+ // the temporary compositing buffer.
+ nsRect bounds = GetClippedBounds(aBuilder);
+ nsRegion visibleUnderChildren;
+ visibleUnderChildren.And(*aVisibleRegion, bounds);
+ return
+ nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
+}
+
+bool nsDisplayOpacity::TryMerge(nsDisplayItem* aItem) {
+ if (aItem->GetType() != TYPE_OPACITY)
+ return false;
+ // items for the same content element should be merged into a single
+ // compositing group
+ // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
+ if (aItem->Frame()->GetContent() != mFrame->GetContent())
+ return false;
+ if (aItem->GetClip() != GetClip())
+ return false;
+ if (aItem->ScrollClip() != ScrollClip())
+ return false;
+ MergeFromTrackingMergedFrames(static_cast<nsDisplayOpacity*>(aItem));
+ return true;
+}
+
+void
+nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (opacity " << mOpacity << ")";
+}
+
+nsDisplayBlendMode::nsDisplayBlendMode(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ uint8_t aBlendMode,
+ const DisplayItemScrollClip* aScrollClip,
+ uint32_t aIndex)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+ , mBlendMode(aBlendMode)
+ , mIndex(aIndex)
+{
+ MOZ_COUNT_CTOR(nsDisplayBlendMode);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayBlendMode::~nsDisplayBlendMode() {
+ MOZ_COUNT_DTOR(nsDisplayBlendMode);
+}
+#endif
+
+nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) {
+ *aSnap = false;
+ // We are never considered opaque
+ return nsRegion();
+}
+
+LayerState
+nsDisplayBlendMode::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ return LAYER_ACTIVE;
+}
+
+// nsDisplayBlendMode uses layers for rendering
+already_AddRefed<Layer>
+nsDisplayBlendMode::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ newContainerParameters, nullptr);
+ if (!container) {
+ return nullptr;
+ }
+
+ container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
+
+ return container.forget();
+}
+
+bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) {
+ // Our children are need their backdrop so we should not allow them to subtract
+ // area from aVisibleRegion. We do need to find out what is visible under
+ // our children in the temporary compositing buffer, because if our children
+ // paint our entire bounds opaquely then we don't need an alpha channel in
+ // the temporary compositing buffer.
+ nsRect bounds = GetClippedBounds(aBuilder);
+ nsRegion visibleUnderChildren;
+ visibleUnderChildren.And(*aVisibleRegion, bounds);
+ return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
+}
+
+bool nsDisplayBlendMode::TryMerge(nsDisplayItem* aItem) {
+ if (aItem->GetType() != TYPE_BLEND_MODE)
+ return false;
+ nsDisplayBlendMode* item = static_cast<nsDisplayBlendMode*>(aItem);
+ // items for the same content element should be merged into a single
+ // compositing group
+ if (item->Frame()->GetContent() != mFrame->GetContent())
+ return false;
+ if (item->mIndex != 0 || mIndex != 0)
+ return false; // don't merge background-blend-mode items
+ if (item->GetClip() != GetClip())
+ return false;
+ if (item->ScrollClip() != ScrollClip())
+ return false;
+ MergeFromTrackingMergedFrames(item);
+ return true;
+}
+
+/* static */ nsDisplayBlendContainer*
+nsDisplayBlendContainer::CreateForMixBlendMode(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip)
+{
+ return new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, aList, aScrollClip, false);
+}
+
+/* static */ nsDisplayBlendContainer*
+nsDisplayBlendContainer::CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip)
+{
+ return new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, aList, aScrollClip, true);
+}
+
+nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip,
+ bool aIsForBackground)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+ , mIsForBackground(aIsForBackground)
+{
+ MOZ_COUNT_CTOR(nsDisplayBlendContainer);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayBlendContainer::~nsDisplayBlendContainer() {
+ MOZ_COUNT_DTOR(nsDisplayBlendContainer);
+}
+#endif
+
+// nsDisplayBlendContainer uses layers for rendering
+already_AddRefed<Layer>
+nsDisplayBlendContainer::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ // turn off anti-aliasing in the parent stacking context because it changes
+ // how the group is initialized.
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+
+ RefPtr<Layer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ newContainerParameters, nullptr);
+ if (!container) {
+ return nullptr;
+ }
+
+ container->SetForceIsolatedGroup(true);
+ return container.forget();
+}
+
+LayerState
+nsDisplayBlendContainer::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot());
+}
+
+bool nsDisplayBlendContainer::TryMerge(nsDisplayItem* aItem) {
+ if (aItem->GetType() != TYPE_BLEND_CONTAINER)
+ return false;
+ // items for the same content element should be merged into a single
+ // compositing group
+ // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
+ if (aItem->Frame()->GetContent() != mFrame->GetContent())
+ return false;
+ if (aItem->GetClip() != GetClip())
+ return false;
+ if (aItem->ScrollClip() != ScrollClip())
+ return false;
+ MergeFromTrackingMergedFrames(static_cast<nsDisplayBlendContainer*>(aItem));
+ return true;
+}
+
+nsDisplayOwnLayer::nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ uint32_t aFlags, ViewID aScrollTarget,
+ float aScrollbarThumbRatio,
+ bool aForceActive)
+ : nsDisplayWrapList(aBuilder, aFrame, aList)
+ , mFlags(aFlags)
+ , mScrollTarget(aScrollTarget)
+ , mScrollbarThumbRatio(aScrollbarThumbRatio)
+ , mForceActive(aForceActive)
+{
+ MOZ_COUNT_CTOR(nsDisplayOwnLayer);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayOwnLayer::~nsDisplayOwnLayer() {
+ MOZ_COUNT_DTOR(nsDisplayOwnLayer);
+}
+#endif
+
+LayerState
+nsDisplayOwnLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ if (mForceActive) {
+ return mozilla::LAYER_ACTIVE_FORCE;
+ }
+
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, mAnimatedGeometryRoot);
+}
+
+// nsDisplayOpacity uses layers for rendering
+already_AddRefed<Layer>
+nsDisplayOwnLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ RefPtr<ContainerLayer> layer = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ aContainerParameters, nullptr,
+ FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+ if (mFlags & VERTICAL_SCROLLBAR) {
+ layer->SetScrollbarData(mScrollTarget, Layer::ScrollDirection::VERTICAL, mScrollbarThumbRatio);
+ }
+ if (mFlags & HORIZONTAL_SCROLLBAR) {
+ layer->SetScrollbarData(mScrollTarget, Layer::ScrollDirection::HORIZONTAL, mScrollbarThumbRatio);
+ }
+ if (mFlags & SCROLLBAR_CONTAINER) {
+ layer->SetIsScrollbarContainer();
+ }
+
+ if (mFlags & GENERATE_SUBDOC_INVALIDATIONS) {
+ mFrame->PresContext()->SetNotifySubDocInvalidationData(layer);
+ }
+ return layer.forget();
+}
+
+nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ uint32_t aFlags)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList, aFlags)
+ , mScrollParentId(aBuilder->GetCurrentScrollParentId())
+{
+ MOZ_COUNT_CTOR(nsDisplaySubDocument);
+ mForceDispatchToContentRegion =
+ aBuilder->IsBuildingLayerEventRegions() &&
+ nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(aFrame->PresContext()->PresShell());
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplaySubDocument::~nsDisplaySubDocument() {
+ MOZ_COUNT_DTOR(nsDisplaySubDocument);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplaySubDocument::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ nsPresContext* presContext = mFrame->PresContext();
+ nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
+ ContainerLayerParameters params = aContainerParameters;
+ if ((mFlags & GENERATE_SCROLLABLE_LAYER) &&
+ rootScrollFrame->GetContent() &&
+ nsLayoutUtils::HasCriticalDisplayPort(rootScrollFrame->GetContent())) {
+ params.mInLowPrecisionDisplayPort = true;
+ }
+
+ RefPtr<Layer> layer = nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, params);
+ layer->AsContainerLayer()->SetEventRegionsOverride(mForceDispatchToContentRegion
+ ? EventRegionsOverride::ForceDispatchToContent
+ : EventRegionsOverride::NoOverride);
+ return layer.forget();
+}
+
+UniquePtr<ScrollMetadata>
+nsDisplaySubDocument::ComputeScrollMetadata(Layer* aLayer,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ if (!(mFlags & GENERATE_SCROLLABLE_LAYER)) {
+ return UniquePtr<ScrollMetadata>(nullptr);
+ }
+
+ nsPresContext* presContext = mFrame->PresContext();
+ nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
+ bool isRootContentDocument = presContext->IsRootContentDocument();
+ nsIPresShell* presShell = presContext->PresShell();
+ ContainerLayerParameters params(
+ aContainerParameters.mXScale * presShell->GetResolution(),
+ aContainerParameters.mYScale * presShell->GetResolution(),
+ nsIntPoint(), aContainerParameters);
+ if ((mFlags & GENERATE_SCROLLABLE_LAYER) &&
+ rootScrollFrame->GetContent() &&
+ nsLayoutUtils::HasCriticalDisplayPort(rootScrollFrame->GetContent())) {
+ params.mInLowPrecisionDisplayPort = true;
+ }
+
+ nsRect viewport = mFrame->GetRect() -
+ mFrame->GetPosition() +
+ mFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+ return MakeUnique<ScrollMetadata>(
+ nsLayoutUtils::ComputeScrollMetadata(
+ mFrame, rootScrollFrame, rootScrollFrame->GetContent(), ReferenceFrame(),
+ aLayer, mScrollParentId, viewport, Nothing(),
+ isRootContentDocument, params));
+}
+
+static bool
+UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+{
+ return aBuilder->IsPaintingToWindow() &&
+ nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext());
+}
+
+nsRect
+nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
+ *aSnap = false;
+ return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame);
+ }
+
+ return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap);
+}
+
+bool
+nsDisplaySubDocument::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion)
+{
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if (!(mFlags & GENERATE_SCROLLABLE_LAYER) || !usingDisplayPort) {
+ return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion);
+ }
+
+ nsRect displayport;
+ nsIFrame* rootScrollFrame = mFrame->PresContext()->PresShell()->GetRootScrollFrame();
+ MOZ_ASSERT(rootScrollFrame);
+ Unused << nsLayoutUtils::GetDisplayPort(rootScrollFrame->GetContent(), &displayport,
+ RelativeTo::ScrollFrame);
+
+ nsRegion childVisibleRegion;
+ // The visible region for the children may be much bigger than the hole we
+ // are viewing the children from, so that the compositor process has enough
+ // content to asynchronously pan while content is being refreshed.
+ childVisibleRegion = displayport + mFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+ nsRect boundedRect =
+ childVisibleRegion.GetBounds().Intersect(mList.GetBounds(aBuilder));
+ bool visible = mList.ComputeVisibilityForSublist(
+ aBuilder, &childVisibleRegion, boundedRect);
+
+ // If APZ is enabled then don't allow this computation to influence
+ // aVisibleRegion, on the assumption that the layer can be asynchronously
+ // scrolled so we'll definitely need all the content under it.
+ if (!nsLayoutUtils::UsesAsyncScrolling(mFrame)) {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ nsRegion removed;
+ removed.Sub(bounds, childVisibleRegion);
+
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+ }
+
+ return visible;
+}
+
+bool
+nsDisplaySubDocument::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+{
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
+ return true;
+ }
+
+ return nsDisplayOwnLayer::ShouldBuildLayerEvenIfInvisible(aBuilder);
+}
+
+nsRegion
+nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+
+ if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap);
+}
+
+nsDisplayResolution::nsDisplayResolution(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ uint32_t aFlags)
+ : nsDisplaySubDocument(aBuilder, aFrame, aList, aFlags) {
+ MOZ_COUNT_CTOR(nsDisplayResolution);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayResolution::~nsDisplayResolution() {
+ MOZ_COUNT_DTOR(nsDisplayResolution);
+}
+#endif
+
+void
+nsDisplayResolution::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsIPresShell* presShell = mFrame->PresContext()->PresShell();
+ nsRect rect = aRect.RemoveResolution(presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
+ mList.HitTest(aBuilder, rect, aState, aOutFrames);
+}
+
+already_AddRefed<Layer>
+nsDisplayResolution::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ nsIPresShell* presShell = mFrame->PresContext()->PresShell();
+ ContainerLayerParameters containerParameters(
+ presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(),
+ aContainerParameters);
+
+ RefPtr<Layer> layer = nsDisplaySubDocument::BuildLayer(
+ aBuilder, aManager, containerParameters);
+ layer->SetPostScale(1.0f / presShell->GetResolution(),
+ 1.0f / presShell->GetResolution());
+ layer->AsContainerLayer()->SetScaleToResolution(
+ presShell->ScaleToResolution(), presShell->GetResolution());
+ return layer.forget();
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList)
+ , mIndex(0)
+ , mIsFixedBackground(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ Init(aBuilder);
+}
+
+nsDisplayFixedPosition::nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList,
+ uint32_t aIndex)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList)
+ , mIndex(aIndex)
+ , mIsFixedBackground(true)
+{
+ MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+ Init(aBuilder);
+}
+
+void
+nsDisplayFixedPosition::Init(nsDisplayListBuilder* aBuilder)
+{
+ mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot;
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
+ }
+}
+
+/* static */ nsDisplayFixedPosition*
+nsDisplayFixedPosition::CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayBackgroundImage* aImage,
+ uint32_t aIndex)
+{
+ // Clear clipping on the child item, since we will apply it to the
+ // fixed position item as well.
+ aImage->SetClip(aBuilder, DisplayItemClip());
+ aImage->SetScrollClip(nullptr);
+
+ nsDisplayList temp;
+ temp.AppendToTop(aImage);
+
+ return new (aBuilder) nsDisplayFixedPosition(aBuilder, aFrame, &temp, aIndex + 1);
+}
+
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayFixedPosition::~nsDisplayFixedPosition() {
+ MOZ_COUNT_DTOR(nsDisplayFixedPosition);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplayFixedPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<Layer> layer =
+ nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+ layer->SetIsFixedPosition(true);
+
+ nsPresContext* presContext = mFrame->PresContext();
+ nsIFrame* fixedFrame = mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : mFrame;
+
+ const nsIFrame* viewportFrame = fixedFrame->GetParent();
+ // anchorRect will be in the container's coordinate system (aLayer's parent layer).
+ // This is the same as the display items' reference frame.
+ nsRect anchorRect;
+ if (viewportFrame) {
+ // Fixed position frames are reflowed into the scroll-port size if one has
+ // been set.
+ if (presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
+ anchorRect.SizeTo(presContext->PresShell()->GetScrollPositionClampingScrollPortSize());
+ } else {
+ anchorRect.SizeTo(viewportFrame->GetSize());
+ }
+ } else {
+ // A display item directly attached to the viewport.
+ // For background-attachment:fixed items, the anchor point is always the
+ // top-left of the viewport currently.
+ viewportFrame = fixedFrame;
+ }
+ // The anchorRect top-left is always the viewport top-left.
+ anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(ReferenceFrame()));
+
+ nsLayoutUtils::SetFixedPositionLayerData(layer,
+ viewportFrame, anchorRect, fixedFrame, presContext, aContainerParameters);
+
+ return layer.forget();
+}
+
+bool nsDisplayFixedPosition::TryMerge(nsDisplayItem* aItem) {
+ if (aItem->GetType() != TYPE_FIXED_POSITION)
+ return false;
+ // Items with the same fixed position frame can be merged.
+ nsDisplayFixedPosition* other = static_cast<nsDisplayFixedPosition*>(aItem);
+ if (other->mFrame != mFrame)
+ return false;
+ if (aItem->GetClip() != GetClip())
+ return false;
+ MergeFromTrackingMergedFrames(other);
+ return true;
+}
+
+nsDisplayStickyPosition::nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList)
+ : nsDisplayOwnLayer(aBuilder, aFrame, aList)
+{
+ MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayStickyPosition::~nsDisplayStickyPosition() {
+ MOZ_COUNT_DTOR(nsDisplayStickyPosition);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplayStickyPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) {
+ RefPtr<Layer> layer =
+ nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+ StickyScrollContainer* stickyScrollContainer = StickyScrollContainer::
+ GetStickyScrollContainerForFrame(mFrame);
+ if (!stickyScrollContainer) {
+ return layer.forget();
+ }
+
+ nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
+ nsPresContext* presContext = scrollFrame->PresContext();
+
+ // Sticky position frames whose scroll frame is the root scroll frame are
+ // reflowed into the scroll-port size if one has been set.
+ nsSize scrollFrameSize = scrollFrame->GetSize();
+ if (scrollFrame == presContext->PresShell()->GetRootScrollFrame() &&
+ presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
+ scrollFrameSize = presContext->PresShell()->
+ GetScrollPositionClampingScrollPortSize();
+ }
+
+ nsLayoutUtils::SetFixedPositionLayerData(layer, scrollFrame,
+ nsRect(scrollFrame->GetOffsetToCrossDoc(ReferenceFrame()), scrollFrameSize),
+ mFrame, presContext, aContainerParameters);
+
+ ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(
+ stickyScrollContainer->ScrollFrame()->GetScrolledFrame()->GetContent());
+
+ float factor = presContext->AppUnitsPerDevPixel();
+ nsRect outer;
+ nsRect inner;
+ stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
+ LayerRect stickyOuter(NSAppUnitsToFloatPixels(outer.x, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(outer.y, factor) *
+ aContainerParameters.mYScale,
+ NSAppUnitsToFloatPixels(outer.width, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(outer.height, factor) *
+ aContainerParameters.mYScale);
+ LayerRect stickyInner(NSAppUnitsToFloatPixels(inner.x, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(inner.y, factor) *
+ aContainerParameters.mYScale,
+ NSAppUnitsToFloatPixels(inner.width, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(inner.height, factor) *
+ aContainerParameters.mYScale);
+ layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner);
+
+ return layer.forget();
+}
+
+bool nsDisplayStickyPosition::TryMerge(nsDisplayItem* aItem) {
+ if (aItem->GetType() != TYPE_STICKY_POSITION)
+ return false;
+ // Items with the same fixed position frame can be merged.
+ nsDisplayStickyPosition* other = static_cast<nsDisplayStickyPosition*>(aItem);
+ if (other->mFrame != mFrame)
+ return false;
+ if (aItem->GetClip() != GetClip())
+ return false;
+ if (aItem->ScrollClip() != ScrollClip())
+ return false;
+ MergeFromTrackingMergedFrames(other);
+ return true;
+}
+
+nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
+ nsDisplayListBuilder* aBuilder,
+ nsIFrame* aScrolledFrame,
+ nsIFrame* aScrollFrame)
+ : nsDisplayWrapList(aBuilder, aScrollFrame)
+ , mScrollFrame(aScrollFrame)
+ , mScrolledFrame(aScrolledFrame)
+ , mScrollParentId(aBuilder->GetCurrentScrollParentId())
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer);
+#endif
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayScrollInfoLayer::~nsDisplayScrollInfoLayer()
+{
+ MOZ_COUNT_DTOR(nsDisplayScrollInfoLayer);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplayScrollInfoLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ // In general for APZ with event-regions we no longer have a need for
+ // scrollinfo layers. However, in some cases, there might be content that
+ // cannot be layerized, and so needs to scroll synchronously. To handle those
+ // cases, we still want to generate scrollinfo layers.
+
+ ContainerLayerParameters params = aContainerParameters;
+ if (mScrolledFrame->GetContent() &&
+ nsLayoutUtils::HasCriticalDisplayPort(mScrolledFrame->GetContent())) {
+ params.mInLowPrecisionDisplayPort = true;
+ }
+
+ return aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ params, nullptr,
+ FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
+}
+
+LayerState
+nsDisplayScrollInfoLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ return LAYER_ACTIVE_EMPTY;
+}
+
+UniquePtr<ScrollMetadata>
+nsDisplayScrollInfoLayer::ComputeScrollMetadata(Layer* aLayer,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ ContainerLayerParameters params = aContainerParameters;
+ if (mScrolledFrame->GetContent() &&
+ nsLayoutUtils::HasCriticalDisplayPort(mScrolledFrame->GetContent())) {
+ params.mInLowPrecisionDisplayPort = true;
+ }
+
+ nsRect viewport = mScrollFrame->GetRect() -
+ mScrollFrame->GetPosition() +
+ mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+ ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata(
+ mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(),
+ ReferenceFrame(), aLayer,
+ mScrollParentId, viewport, Nothing(), false, params);
+ metadata.GetMetrics().SetIsScrollInfoLayer(true);
+
+ return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata));
+}
+
+
+
+void
+nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream)
+{
+ aStream << " (scrollframe " << mScrollFrame
+ << " scrolledFrame " << mScrolledFrame << ")";
+}
+
+nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ int32_t aAPD, int32_t aParentAPD,
+ uint32_t aFlags)
+ : nsDisplaySubDocument(aBuilder, aFrame, aList, aFlags)
+ , mAPD(aAPD), mParentAPD(aParentAPD) {
+ MOZ_COUNT_CTOR(nsDisplayZoom);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayZoom::~nsDisplayZoom() {
+ MOZ_COUNT_DTOR(nsDisplayZoom);
+}
+#endif
+
+nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap);
+ *aSnap = false;
+ return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD);
+}
+
+void nsDisplayZoom::HitTest(nsDisplayListBuilder *aBuilder,
+ const nsRect& aRect,
+ HitTestState *aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsRect rect;
+ // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
+ // rect as well instead of possibly rounding the width or height to zero.
+ if (aRect.width == 1 && aRect.height == 1) {
+ rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD));
+ rect.width = rect.height = 1;
+ } else {
+ rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ }
+ mList.HitTest(aBuilder, rect, aState, aOutFrames);
+}
+
+bool nsDisplayZoom::ComputeVisibility(nsDisplayListBuilder *aBuilder,
+ nsRegion *aVisibleRegion)
+{
+ // Convert the passed in visible region to our appunits.
+ nsRegion visibleRegion;
+ // mVisibleRect has been clipped to GetClippedBounds
+ visibleRegion.And(*aVisibleRegion, mVisibleRect);
+ visibleRegion = visibleRegion.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ nsRegion originalVisibleRegion = visibleRegion;
+
+ nsRect transformedVisibleRect =
+ mVisibleRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
+ bool retval;
+ // If we are to generate a scrollable layer we call
+ // nsDisplaySubDocument::ComputeVisibility to make the necessary adjustments
+ // for ComputeVisibility, it does all it's calculations in the child APD.
+ bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
+ if (!(mFlags & GENERATE_SCROLLABLE_LAYER) || !usingDisplayPort) {
+ retval =
+ mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion,
+ transformedVisibleRect);
+ } else {
+ retval =
+ nsDisplaySubDocument::ComputeVisibility(aBuilder, &visibleRegion);
+ }
+
+ nsRegion removed;
+ // removed = originalVisibleRegion - visibleRegion
+ removed.Sub(originalVisibleRegion, visibleRegion);
+ // Convert removed region to parent appunits.
+ removed = removed.ScaleToOtherAppUnitsRoundIn(mAPD, mParentAPD);
+ // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications
+ // SubtractFromVisibleRegion does)
+ aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
+
+ return retval;
+}
+
+///////////////////////////////////////////////////
+// nsDisplayTransform Implementation
+//
+
+// Write #define UNIFIED_CONTINUATIONS here and in
+// TransformReferenceBox::Initialize to have the transform property try
+// to transform content with continuations as one unified block instead of
+// several smaller ones. This is currently disabled because it doesn't work
+// correctly, since when the frames are initially being reflowed, their
+// continuations all compute their bounding rects independently of each other
+// and consequently get the wrong value. Write #define DEBUG_HIT here to have
+// the nsDisplayTransform class dump out a bunch of information about hit
+// detection.
+#undef UNIFIED_CONTINUATIONS
+#undef DEBUG_HIT
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame *aFrame, nsDisplayList *aList,
+ const nsRect& aChildrenVisibleRect,
+ ComputeTransformFunction aTransformGetter,
+ uint32_t aIndex)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mStoredList(aBuilder, aFrame, aList)
+ , mTransformGetter(aTransformGetter)
+ , mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot)
+ , mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot)
+ , mChildrenVisibleRect(aChildrenVisibleRect)
+ , mIndex(aIndex)
+ , mNoExtendContext(false)
+ , mIsTransformSeparator(false)
+ , mTransformPreserves3DInited(false)
+ , mIsFullyVisible(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ Init(aBuilder);
+}
+
+void
+nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder)
+{
+ if (mFrame == aBuilder->RootReferenceFrame()) {
+ return;
+ }
+ nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
+ mReferenceFrame =
+ aBuilder->FindReferenceFrameFor(outerFrame);
+ mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(mFrame)) {
+ // This is an odd special case. If we are both IsFixedPosFrameInDisplayPort
+ // and transformed that we are our own AGR parent.
+ // We want our frame to be our AGR because FrameLayerBuilder uses our AGR to
+ // determine if we are inside a fixed pos subtree. If we use the outer AGR
+ // from outside the fixed pos subtree FLB can't tell that we are fixed pos.
+ mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren;
+ } else if (mFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY &&
+ IsStickyFrameActive(aBuilder, mFrame, nullptr)) {
+ // Similar to the IsFixedPosFrameInDisplayPort case we are our own AGR.
+ // We are inside the sticky position, so our AGR is the sticky positioned
+ // frame, which is our AGR, not the parent AGR.
+ mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren;
+ } else if (mAnimatedGeometryRoot->mParentAGR) {
+ mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot->mParentAGR;
+ if (!MayBeAnimated(aBuilder)) {
+ // If we're an animated transform then we want the same AGR as our children
+ // so that FrameLayerBuilder knows that this layer moves with the transform
+ // and won't compute occlusions. If we're not animated then use our parent
+ // AGR so that inactive transform layers can go in the same PaintedLayer as
+ // surrounding content.
+ mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR;
+ }
+ }
+ mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame;
+}
+
+void
+nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder)
+{
+ mHasBounds = false;
+ mStoredList.SetClip(aBuilder, DisplayItemClip::NoClip());
+ mStoredList.SetVisibleRect(mChildrenVisibleRect);
+}
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame *aFrame, nsDisplayList *aList,
+ const nsRect& aChildrenVisibleRect,
+ uint32_t aIndex,
+ bool aIsFullyVisible)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mStoredList(aBuilder, aFrame, aList)
+ , mTransformGetter(nullptr)
+ , mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot)
+ , mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot)
+ , mChildrenVisibleRect(aChildrenVisibleRect)
+ , mIndex(aIndex)
+ , mNoExtendContext(false)
+ , mIsTransformSeparator(false)
+ , mTransformPreserves3DInited(false)
+ , mIsFullyVisible(aIsFullyVisible)
+{
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ SetReferenceFrameToAncestor(aBuilder);
+ Init(aBuilder);
+ UpdateBoundsFor3D(aBuilder);
+}
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame *aFrame, nsDisplayItem *aItem,
+ const nsRect& aChildrenVisibleRect,
+ uint32_t aIndex)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mStoredList(aBuilder, aFrame, aItem)
+ , mTransformGetter(nullptr)
+ , mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot)
+ , mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot)
+ , mChildrenVisibleRect(aChildrenVisibleRect)
+ , mIndex(aIndex)
+ , mNoExtendContext(false)
+ , mIsTransformSeparator(false)
+ , mTransformPreserves3DInited(false)
+ , mIsFullyVisible(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ SetReferenceFrameToAncestor(aBuilder);
+ Init(aBuilder);
+}
+
+nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame *aFrame, nsDisplayList *aList,
+ const nsRect& aChildrenVisibleRect,
+ const Matrix4x4& aTransform,
+ uint32_t aIndex)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mStoredList(aBuilder, aFrame, aList)
+ , mTransform(aTransform)
+ , mTransformGetter(nullptr)
+ , mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot)
+ , mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot)
+ , mChildrenVisibleRect(aChildrenVisibleRect)
+ , mIndex(aIndex)
+ , mNoExtendContext(false)
+ , mIsTransformSeparator(true)
+ , mTransformPreserves3DInited(false)
+ , mIsFullyVisible(false)
+{
+ MOZ_COUNT_CTOR(nsDisplayTransform);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ Init(aBuilder);
+ UpdateBoundsFor3D(aBuilder);
+}
+
+/* Returns the delta specified by the transform-origin property.
+ * This is a positive delta, meaning that it indicates the direction to move
+ * to get from (0, 0) of the frame to the transform origin. This function is
+ * called off the main thread.
+ */
+/* static */ Point3D
+nsDisplayTransform::GetDeltaToTransformOrigin(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ const nsRect* aBoundsOverride)
+{
+ NS_PRECONDITION(aFrame, "Can't get delta for a null frame!");
+ NS_PRECONDITION(aFrame->IsTransformed() ||
+ aFrame->BackfaceIsHidden() ||
+ aFrame->Combines3DTransformWithAncestors(),
+ "Shouldn't get a delta for an untransformed frame!");
+
+ if (!aFrame->IsTransformed()) {
+ return Point3D();
+ }
+
+ /* For both of the coordinates, if the value of transform is a
+ * percentage, it's relative to the size of the frame. Otherwise, if it's
+ * a distance, it's already computed for us!
+ */
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ // We don't use aBoundsOverride for SVG since we need to account for
+ // refBox.X/Y(). This happens to work because ReflowSVG sets the frame's
+ // mRect before calling FinishAndStoreOverflow so we don't need the override.
+ TransformReferenceBox refBox;
+ if (aBoundsOverride &&
+ !(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ refBox.Init(aBoundsOverride->Size());
+ } else {
+ refBox.Init(aFrame);
+ }
+
+ /* Allows us to access dimension getters by index. */
+ float coords[2];
+ TransformReferenceBox::DimensionGetter dimensionGetter[] =
+ { &TransformReferenceBox::Width, &TransformReferenceBox::Height };
+ TransformReferenceBox::DimensionGetter offsetGetter[] =
+ { &TransformReferenceBox::X, &TransformReferenceBox::Y };
+
+ for (uint8_t index = 0; index < 2; ++index) {
+ /* If the transform-origin specifies a percentage, take the percentage
+ * of the size of the box.
+ */
+ const nsStyleCoord &coord = display->mTransformOrigin[index];
+ if (coord.GetUnit() == eStyleUnit_Calc) {
+ const nsStyleCoord::Calc *calc = coord.GetCalcValue();
+ coords[index] =
+ NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
+ calc->mPercent +
+ NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
+ } else if (coord.GetUnit() == eStyleUnit_Percent) {
+ coords[index] =
+ NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
+ coord.GetPercentValue();
+ } else {
+ MOZ_ASSERT(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+ coords[index] =
+ NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
+ }
+
+ if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
+ // SVG frames (unlike other frames) have a reference box that can be (and
+ // typically is) offset from the TopLeft() of the frame. We need to
+ // account for that here.
+ coords[index] +=
+ NSAppUnitsToFloatPixels((refBox.*offsetGetter[index])(), aAppUnitsPerPixel);
+ }
+ }
+
+ return Point3D(coords[0], coords[1],
+ NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
+ aAppUnitsPerPixel));
+}
+
+/* static */ bool
+nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ Matrix4x4& aOutMatrix)
+{
+ NS_PRECONDITION(aFrame, "Can't get delta for a null frame!");
+ NS_PRECONDITION(aFrame->IsTransformed() ||
+ aFrame->BackfaceIsHidden() ||
+ aFrame->Combines3DTransformWithAncestors(),
+ "Shouldn't get a delta for an untransformed frame!");
+ NS_PRECONDITION(aOutMatrix.IsIdentity(), "Must have a blank output matrix");
+
+ if (!aFrame->IsTransformed()) {
+ return false;
+ }
+
+ /* Find our containing block, which is the element that provides the
+ * value for perspective we need to use
+ */
+
+ //TODO: Is it possible that the cbFrame's bounds haven't been set correctly yet
+ // (similar to the aBoundsOverride case for GetResultingTransformMatrix)?
+ nsIFrame* cbFrame = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
+ if (!cbFrame) {
+ return false;
+ }
+
+ /* Grab the values for perspective and perspective-origin (if present) */
+
+ const nsStyleDisplay* cbDisplay = cbFrame->StyleDisplay();
+ if (cbDisplay->mChildPerspective.GetUnit() != eStyleUnit_Coord) {
+ return false;
+ }
+ nscoord perspective = cbDisplay->mChildPerspective.GetCoordValue();
+ if (perspective < 0) {
+ return true;
+ }
+
+ TransformReferenceBox refBox(cbFrame);
+
+ /* Allows us to access named variables by index. */
+ Point3D perspectiveOrigin;
+ gfx::Float* coords[2] = {&perspectiveOrigin.x, &perspectiveOrigin.y};
+ TransformReferenceBox::DimensionGetter dimensionGetter[] =
+ { &TransformReferenceBox::Width, &TransformReferenceBox::Height };
+
+ /* For both of the coordinates, if the value of perspective-origin is a
+ * percentage, it's relative to the size of the frame. Otherwise, if it's
+ * a distance, it's already computed for us!
+ */
+ for (uint8_t index = 0; index < 2; ++index) {
+ /* If the -transform-origin specifies a percentage, take the percentage
+ * of the size of the box.
+ */
+ const nsStyleCoord &coord = cbDisplay->mPerspectiveOrigin[index];
+ if (coord.GetUnit() == eStyleUnit_Calc) {
+ const nsStyleCoord::Calc *calc = coord.GetCalcValue();
+ *coords[index] =
+ NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
+ calc->mPercent +
+ NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
+ } else if (coord.GetUnit() == eStyleUnit_Percent) {
+ *coords[index] =
+ NSAppUnitsToFloatPixels((refBox.*dimensionGetter[index])(), aAppUnitsPerPixel) *
+ coord.GetPercentValue();
+ } else {
+ MOZ_ASSERT(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+ *coords[index] =
+ NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
+ }
+ }
+
+ /* GetOffsetTo computes the offset required to move from 0,0 in cbFrame to 0,0
+ * in aFrame. Although we actually want the inverse of this, it's faster to
+ * compute this way.
+ */
+ nsPoint frameToCbOffset = -aFrame->GetOffsetTo(cbFrame);
+ Point3D frameToCbGfxOffset(
+ NSAppUnitsToFloatPixels(frameToCbOffset.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(frameToCbOffset.y, aAppUnitsPerPixel),
+ 0.0f);
+
+ /* Move the perspective origin to be relative to aFrame, instead of relative
+ * to the containing block which is how it was specified in the style system.
+ */
+ perspectiveOrigin += frameToCbGfxOffset;
+
+ Float perspectivePx = std::max(NSAppUnitsToFloatPixels(perspective,
+ aAppUnitsPerPixel),
+ std::numeric_limits<Float>::epsilon());
+ aOutMatrix._34 = -1.0 / perspectivePx;
+ aOutMatrix.ChangeBasis(perspectiveOrigin);
+ return true;
+}
+
+nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ const nsRect* aBoundsOverride)
+ : mFrame(aFrame)
+ , mTransformList(aFrame->StyleDisplay()->mSpecifiedTransform)
+ , mToTransformOrigin(GetDeltaToTransformOrigin(aFrame, aAppUnitsPerPixel, aBoundsOverride))
+{
+}
+
+/* Wraps up the transform matrix in a change-of-basis matrix pair that
+ * translates from local coordinate space to transform coordinate space, then
+ * hands it back.
+ */
+Matrix4x4
+nsDisplayTransform::GetResultingTransformMatrix(const FrameTransformProperties& aProperties,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride)
+{
+ return GetResultingTransformMatrixInternal(aProperties, aOrigin, aAppUnitsPerPixel,
+ aFlags, aBoundsOverride);
+}
+
+Matrix4x4
+nsDisplayTransform::GetResultingTransformMatrix(const nsIFrame* aFrame,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride)
+{
+ FrameTransformProperties props(aFrame,
+ aAppUnitsPerPixel,
+ aBoundsOverride);
+
+ return GetResultingTransformMatrixInternal(props, aOrigin, aAppUnitsPerPixel,
+ aFlags, aBoundsOverride);
+}
+
+Matrix4x4
+nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride)
+{
+ const nsIFrame *frame = aProperties.mFrame;
+ NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE), "Must have a frame to compute perspective!");
+
+ // Get the underlying transform matrix:
+
+ // We don't use aBoundsOverride for SVG since we need to account for
+ // refBox.X/Y(). This happens to work because ReflowSVG sets the frame's
+ // mRect before calling FinishAndStoreOverflow so we don't need the override.
+ TransformReferenceBox refBox;
+ if (aBoundsOverride &&
+ (!frame || !(frame->GetStateBits() & NS_FRAME_SVG_LAYOUT))) {
+ refBox.Init(aBoundsOverride->Size());
+ } else {
+ refBox.Init(frame);
+ }
+
+ /* Get the matrix, then change its basis to factor in the origin. */
+ RuleNodeCacheConditions dummy;
+ bool dummyBool;
+ Matrix4x4 result;
+ // Call IsSVGTransformed() regardless of the value of
+ // disp->mSpecifiedTransform, since we still need any transformFromSVGParent.
+ Matrix svgTransform, transformFromSVGParent;
+ bool hasSVGTransforms =
+ frame && frame->IsSVGTransformed(&svgTransform, &transformFromSVGParent);
+ bool hasTransformFromSVGParent =
+ hasSVGTransforms && !transformFromSVGParent.IsIdentity();
+ /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
+ if (aProperties.mTransformList) {
+ result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
+ frame ? frame->StyleContext() : nullptr,
+ frame ? frame->PresContext() : nullptr,
+ dummy, refBox, aAppUnitsPerPixel,
+ &dummyBool);
+ } else if (hasSVGTransforms) {
+ // Correct the translation components for zoom:
+ float pixelsPerCSSPx = frame->PresContext()->AppUnitsPerCSSPixel() /
+ aAppUnitsPerPixel;
+ svgTransform._31 *= pixelsPerCSSPx;
+ svgTransform._32 *= pixelsPerCSSPx;
+ result = Matrix4x4::From2D(svgTransform);
+ }
+
+
+ Matrix4x4 perspectiveMatrix;
+ bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE;
+ if (hasPerspective) {
+ hasPerspective = ComputePerspectiveMatrix(frame, aAppUnitsPerPixel,
+ perspectiveMatrix);
+ }
+
+ if (!hasSVGTransforms || !hasTransformFromSVGParent) {
+ // This is a simplification of the following |else| block, the
+ // simplification being possible because we don't need to apply
+ // mToTransformOrigin between two transforms.
+ result.ChangeBasis(aProperties.mToTransformOrigin);
+ } else {
+ Point3D refBoxOffset(NSAppUnitsToFloatPixels(refBox.X(), aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(refBox.Y(), aAppUnitsPerPixel),
+ 0);
+ // We have both a transform and children-only transform. The
+ // 'transform-origin' must apply between the two, so we need to apply it
+ // now before we apply transformFromSVGParent. Since mToTransformOrigin is
+ // relative to the frame's TopLeft(), we need to convert it to SVG user
+ // space by subtracting refBoxOffset. (Then after applying
+ // transformFromSVGParent we have to reapply refBoxOffset below.)
+ result.ChangeBasis(aProperties.mToTransformOrigin - refBoxOffset);
+
+ // Now apply the children-only transforms, converting the translation
+ // components to device pixels:
+ float pixelsPerCSSPx =
+ frame->PresContext()->AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
+ transformFromSVGParent._31 *= pixelsPerCSSPx;
+ transformFromSVGParent._32 *= pixelsPerCSSPx;
+ result = result * Matrix4x4::From2D(transformFromSVGParent);
+
+ // Similar to the code in the |if| block above, but since we've accounted
+ // for mToTransformOrigin so we don't include that. We also need to reapply
+ // refBoxOffset.
+ result.ChangeBasis(refBoxOffset);
+ }
+
+ if (hasPerspective) {
+ result = result * perspectiveMatrix;
+ }
+
+ if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) &&
+ frame && frame->Combines3DTransformWithAncestors()) {
+ // Include the transform set on our parent
+ NS_ASSERTION(frame->GetParent() &&
+ frame->GetParent()->IsTransformed() &&
+ frame->GetParent()->Extend3DContext(),
+ "Preserve3D mismatch!");
+ FrameTransformProperties props(frame->GetParent(),
+ aAppUnitsPerPixel,
+ nullptr);
+
+ uint32_t flags = aFlags & (INCLUDE_PRESERVE3D_ANCESTORS|INCLUDE_PERSPECTIVE);
+
+ // If this frame isn't transformed (but we exist for backface-visibility),
+ // then we're not a reference frame so no offset to origin will be added.
+ // Otherwise we need to manually translate into our parent's coordinate
+ // space.
+ if (frame->IsTransformed()) {
+ nsLayoutUtils::PostTranslate(result, frame->GetPosition(), aAppUnitsPerPixel, !hasSVGTransforms);
+ }
+ Matrix4x4 parent =
+ GetResultingTransformMatrixInternal(props,
+ nsPoint(0, 0),
+ aAppUnitsPerPixel, flags,
+ nullptr);
+ result = result * parent;
+ }
+
+ if (aFlags & OFFSET_BY_ORIGIN) {
+ nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel, !hasSVGTransforms);
+ }
+
+ return result;
+}
+
+bool
+nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
+{
+ if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_opacity)) {
+ return true;
+ }
+
+ EffectCompositor::SetPerformanceWarning(
+ mFrame, eCSSProperty_opacity,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::OpacityFrameInactive));
+
+ return false;
+}
+
+bool
+nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
+{
+ return mIsFullyVisible;
+}
+
+/* static */ bool
+nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+{
+ // Elements whose transform has been modified recently, or which
+ // have a compositor-animated transform, can be prerendered. An element
+ // might have only just had its transform animated in which case
+ // the ActiveLayerManager may not have been notified yet.
+ if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) &&
+ !EffectCompositor::HasAnimationsForCompositor(aFrame,
+ eCSSProperty_transform)) {
+ EffectCompositor::SetPerformanceWarning(
+ aFrame, eCSSProperty_transform,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::TransformFrameInactive));
+
+ return false;
+ }
+
+ nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();
+ // Only prerender if the transformed frame's size is <= the
+ // reference frame size (~viewport), allowing a 1/8th fuzz factor
+ // for shadows, borders, etc.
+ refSize += nsSize(refSize.width / 8, refSize.height / 8);
+ gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(aFrame);
+ nsSize frameSize = nsSize(
+ aFrame->GetVisualOverflowRectRelativeToSelf().Size().width * scale.width,
+ aFrame->GetVisualOverflowRectRelativeToSelf().Size().height * scale.height);
+ nscoord maxInAppUnits = nscoord_MAX;
+ if (frameSize <= refSize) {
+ maxInAppUnits = aFrame->PresContext()->DevPixelsToAppUnits(4096);
+ if (frameSize <= nsSize(maxInAppUnits, maxInAppUnits)) {
+ return true;
+ }
+ }
+
+ nsRect visual = aFrame->GetVisualOverflowRect();
+
+
+ EffectCompositor::SetPerformanceWarning(
+ aFrame, eCSSProperty_transform,
+ AnimationPerformanceWarning(
+ AnimationPerformanceWarning::Type::ContentTooLarge,
+ {
+ nsPresContext::AppUnitsToIntCSSPixels(frameSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(frameSize.height),
+ nsPresContext::AppUnitsToIntCSSPixels(refSize.width),
+ nsPresContext::AppUnitsToIntCSSPixels(refSize.height),
+ nsPresContext::AppUnitsToIntCSSPixels(visual.width),
+ nsPresContext::AppUnitsToIntCSSPixels(visual.height),
+ nsPresContext::AppUnitsToIntCSSPixels(maxInAppUnits)
+ }));
+ return false;
+}
+
+/* If the matrix is singular, or a hidden backface is shown, the frame won't be visible or hit. */
+static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix)
+{
+ if (aMatrix.IsSingular()) {
+ return false;
+ }
+ if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) {
+ return false;
+ }
+ return true;
+}
+
+const Matrix4x4&
+nsDisplayTransform::GetTransform()
+{
+ if (mTransform.IsIdentity()) {
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Point3D newOrigin =
+ Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale),
+ NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale),
+ 0.0f);
+ if (mTransformGetter) {
+ mTransform = mTransformGetter(mFrame, scale);
+ mTransform.ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z);
+ } else if (!mIsTransformSeparator) {
+ DebugOnly<bool> isReference =
+ mFrame->IsTransformed() ||
+ mFrame->Combines3DTransformWithAncestors() || mFrame->Extend3DContext();
+ MOZ_ASSERT(isReference);
+ mTransform =
+ GetResultingTransformMatrix(mFrame, ToReferenceFrame(),
+ scale, INCLUDE_PERSPECTIVE|OFFSET_BY_ORIGIN);
+ }
+ }
+ return mTransform;
+}
+
+Matrix4x4
+nsDisplayTransform::GetTransformForRendering()
+{
+ if (!mFrame->HasPerspective() || mTransformGetter || mIsTransformSeparator) {
+ return GetTransform();
+ }
+ MOZ_ASSERT(!mTransformGetter);
+
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ // Don't include perspective transform, or the offset to origin, since
+ // nsDisplayPerspective will handle both of those.
+ return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0);
+}
+
+const Matrix4x4&
+nsDisplayTransform::GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder)
+{
+ MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext());
+ // XXX: should go back to fix mTransformGetter.
+ if (!mTransformPreserves3DInited) {
+ mTransformPreserves3DInited = true;
+ if (!IsLeafOf3DContext()) {
+ mTransformPreserves3D = GetTransform();
+ return mTransformPreserves3D;
+ }
+
+ const nsIFrame* establisher; // Establisher of the 3D rendering context.
+ for (establisher = mFrame;
+ establisher && establisher->Combines3DTransformWithAncestors();
+ establisher = nsLayoutUtils::GetCrossDocParentFrame(establisher)) {
+ }
+ const nsIFrame* establisherReference =
+ aBuilder->FindReferenceFrameFor(nsLayoutUtils::GetCrossDocParentFrame(establisher));
+
+ nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference);
+ float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
+ uint32_t flags = INCLUDE_PRESERVE3D_ANCESTORS|INCLUDE_PERSPECTIVE|OFFSET_BY_ORIGIN;
+ mTransformPreserves3D =
+ GetResultingTransformMatrix(mFrame, offset, scale, flags);
+ }
+ return mTransformPreserves3D;
+}
+
+bool
+nsDisplayTransform::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+{
+ // The visible rect of a Preserves-3D frame is just an intermediate
+ // result. It should always build a layer to make sure it is
+ // rendering correctly.
+ return MayBeAnimated(aBuilder) || mFrame->Combines3DTransformWithAncestors();
+}
+
+already_AddRefed<Layer> nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBuilder,
+ LayerManager *aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ // While generating a glyph mask, the transform vector of the root frame had
+ // been applied into the target context, so stop applying it again here.
+ const bool shouldSkipTransform =
+ (aBuilder->RootReferenceFrame() == mFrame) &&
+ (aBuilder->IsForGenerateGlyphMask() || aBuilder->IsForPaintingSelectionBG());
+
+ /* For frames without transform, it would not be removed for
+ * backface hidden here. But, it would be removed by the init
+ * function of nsDisplayTransform.
+ */
+ const Matrix4x4& newTransformMatrix =
+ shouldSkipTransform ? Matrix4x4(): GetTransformForRendering();
+
+ uint32_t flags = FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR;
+ RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mStoredList.GetChildren(),
+ aContainerParameters, &newTransformMatrix, flags);
+
+ if (!container) {
+ return nullptr;
+ }
+
+ // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all flags,
+ // so we never need to explicitely unset this flag.
+ if (mFrame->Extend3DContext() && !mNoExtendContext) {
+ container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_EXTEND_3D_CONTEXT);
+ } else {
+ container->SetContentFlags(container->GetContentFlags() & ~Layer::CONTENT_EXTEND_3D_CONTEXT);
+ }
+
+ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder,
+ this, mFrame,
+ eCSSProperty_transform);
+ if (mIsFullyVisible && MayBeAnimated(aBuilder)) {
+ // Only allow async updates to the transform if we're an animated layer, since that's what
+ // triggers us to set the correct AGR in the constructor and makes sure FrameLayerBuilder
+ // won't compute occlusions for this layer.
+ container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
+ /*the value is irrelevant*/nullptr);
+ container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_MAY_CHANGE_TRANSFORM);
+ } else {
+ container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
+ container->SetContentFlags(container->GetContentFlags() & ~Layer::CONTENT_MAY_CHANGE_TRANSFORM);
+ }
+ return container.forget();
+}
+
+bool
+nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder)
+{
+ // Here we check if the *post-transform* bounds of this item are big enough
+ // to justify an active layer.
+ if (ActiveLayerTracker::IsStyleAnimated(aBuilder,
+ mFrame,
+ eCSSProperty_transform) ||
+ EffectCompositor::HasAnimationsForCompositor(mFrame,
+ eCSSProperty_transform)) {
+ if (!IsItemTooSmallForActiveLayer(mFrame)) {
+ return true;
+ }
+ SetAnimationPerformanceWarningForTooSmallItem(mFrame, eCSSProperty_transform);
+ }
+ return false;
+}
+
+nsDisplayItem::LayerState
+nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) {
+ // If the transform is 3d, the layer takes part in preserve-3d
+ // sorting, or the layer is a separator then we *always* want this
+ // to be an active layer.
+ if (!GetTransform().Is2D() || mFrame->Combines3DTransformWithAncestors() ||
+ mIsTransformSeparator) {
+ return LAYER_ACTIVE_FORCE;
+ }
+
+ if (MayBeAnimated(aBuilder)) {
+ // Returns LAYER_ACTIVE_FORCE to avoid flatterning the layer for async
+ // animations.
+ return LAYER_ACTIVE_FORCE;
+ }
+
+ // Expect the child display items to have this frame as their animated
+ // geometry root (since it will be their reference frame). If they have a
+ // different animated geometry root, we'll make this an active layer so the
+ // animation can be accelerated.
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters,
+ *mStoredList.GetChildren(), mAnimatedGeometryRootForChildren);
+}
+
+bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder,
+ nsRegion *aVisibleRegion)
+{
+ /* As we do this, we need to be sure to
+ * untransform the visible rect, since we want everything that's painting to
+ * think that it's painting in its original rectangular coordinate space.
+ * If we can't untransform, take the entire overflow rect */
+ nsRect untransformedVisibleRect;
+ if (!UntransformVisibleRect(aBuilder, &untransformedVisibleRect))
+ {
+ untransformedVisibleRect = mFrame->GetVisualOverflowRectRelativeToSelf();
+ }
+ nsRegion untransformedVisible = untransformedVisibleRect;
+ // Call RecomputeVisiblity instead of ComputeVisibility since
+ // nsDisplayItem::ComputeVisibility should only be called from
+ // nsDisplayList::ComputeVisibility (which sets mVisibleRect on the item)
+ mStoredList.RecomputeVisibility(aBuilder, &untransformedVisible);
+ return true;
+}
+
+#ifdef DEBUG_HIT
+#include <time.h>
+#endif
+
+/* HitTest does some fun stuff with matrix transforms to obtain the answer. */
+void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder,
+ const nsRect& aRect,
+ HitTestState *aState,
+ nsTArray<nsIFrame*> *aOutFrames)
+{
+ if (aState->mInPreserves3D) {
+ mStoredList.HitTest(aBuilder, aRect, aState, aOutFrames);
+ return;
+ }
+
+ /* Here's how this works:
+ * 1. Get the matrix. If it's singular, abort (clearly we didn't hit
+ * anything).
+ * 2. Invert the matrix.
+ * 3. Use it to transform the rect into the correct space.
+ * 4. Pass that rect down through to the list's version of HitTest.
+ */
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);
+
+ if (!IsFrameVisible(mFrame, matrix)) {
+ return;
+ }
+
+ /* We want to go from transformed-space to regular space.
+ * Thus we have to invert the matrix, which normally does
+ * the reverse operation (e.g. regular->transformed)
+ */
+
+ /* Now, apply the transform and pass it down the channel. */
+ matrix.Invert();
+ nsRect resultingRect;
+ if (aRect.width == 1 && aRect.height == 1) {
+ // Magic width/height indicating we're hit testing a point, not a rect
+ Point4D point = matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor),
+ NSAppUnitsToFloatPixels(aRect.y, factor)));
+ if (!point.HasPositiveWCoord()) {
+ return;
+ }
+
+ Point point2d = point.As2DPoint();
+
+ resultingRect = nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor),
+ NSFloatPixelsToAppUnits(float(point2d.y), factor),
+ 1, 1);
+
+ } else {
+ Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor),
+ NSAppUnitsToFloatPixels(aRect.y, factor),
+ NSAppUnitsToFloatPixels(aRect.width, factor),
+ NSAppUnitsToFloatPixels(aRect.height, factor));
+
+
+ bool snap;
+ nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap);
+ Rect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+ NSAppUnitsToFloatPixels(childBounds.y, factor),
+ NSAppUnitsToFloatPixels(childBounds.width, factor),
+ NSAppUnitsToFloatPixels(childBounds.height, factor));
+
+ Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds);
+
+ resultingRect = nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Y()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Width()), factor),
+ NSFloatPixelsToAppUnits(float(rect.Height()), factor));
+ }
+
+ if (resultingRect.IsEmpty()) {
+ return;
+ }
+
+
+#ifdef DEBUG_HIT
+ printf("Frame: %p\n", dynamic_cast<void *>(mFrame));
+ printf(" Untransformed point: (%f, %f)\n", resultingRect.X(), resultingRect.Y());
+ uint32_t originalFrameCount = aOutFrames.Length();
+#endif
+
+ mStoredList.HitTest(aBuilder, resultingRect, aState, aOutFrames);
+
+#ifdef DEBUG_HIT
+ if (originalFrameCount != aOutFrames.Length())
+ printf(" Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()),
+ dynamic_cast<void *>(aOutFrames.ElementAt(0)));
+ printf("=== end of hit test ===\n");
+#endif
+
+}
+
+float
+nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint)
+{
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);
+
+ NS_ASSERTION(IsFrameVisible(mFrame, matrix),
+ "We can't have hit a frame that isn't visible!");
+
+ Matrix4x4 inverse = matrix;
+ inverse.Invert();
+ Point4D point = inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor),
+ NSAppUnitsToFloatPixels(aPoint.y, factor)));
+
+ Point point2d = point.As2DPoint();
+
+ Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0));
+ return transformed.z;
+}
+
+/* The bounding rectangle for the object is the overflow rectangle translated
+ * by the reference point.
+ */
+nsRect
+nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = false;
+
+ if (mHasBounds) {
+ return mBounds;
+ }
+
+ if (mFrame->Extend3DContext() && !mIsTransformSeparator) {
+ return nsRect();
+ }
+
+ nsRect untransformedBounds = mStoredList.GetBounds(aBuilder, aSnap);
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ mBounds = nsLayoutUtils::MatrixTransformRect(untransformedBounds,
+ GetTransform(),
+ factor);
+ mHasBounds = true;
+ return mBounds;
+}
+
+void
+nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder)
+{
+ MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext());
+
+ /* For some cases, the transform would make an empty bounds, but it
+ * may be turned back again to get a non-empty bounds. We should
+ * not depend on transforming bounds level by level.
+ *
+ * Here, it applies accumulated transforms on the leaf frames of the
+ * 3d rendering context, and track and accmulate bounds at
+ * nsDisplayListBuilder.
+ */
+ nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
+
+ accTransform.Accumulate(GetTransform());
+
+ if (!IsLeafOf3DContext()) {
+ // Do not dive into another 3D context.
+ mStoredList.DoUpdateBoundsPreserves3D(aBuilder);
+ }
+
+ /* For Preserves3D, it is bounds of only children as leaf frames.
+ * For non-leaf frames, their bounds are accumulated and kept at
+ * nsDisplayListBuilder.
+ */
+ bool snap;
+ nsRect untransformedBounds = mStoredList.GetBounds(aBuilder, &snap);
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect rect =
+ nsLayoutUtils::MatrixTransformRect(untransformedBounds,
+ accTransform.GetCurrentTransform(),
+ factor);
+
+ aBuilder->AccumulateRect(rect);
+}
+
+/* The transform is opaque iff the transform consists solely of scales and
+ * translations and if the underlying content is opaque. Thus if the transform
+ * is of the form
+ *
+ * |a c e|
+ * |b d f|
+ * |0 0 1|
+ *
+ * We need b and c to be zero.
+ *
+ * We also need to check whether the underlying opaque content completely fills
+ * our visible rect. We use UntransformRect which expands to the axis-aligned
+ * bounding rect, but that's OK since if
+ * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it
+ * certainly contains the actual (non-axis-aligned) untransformed rect.
+ */
+nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
+ bool* aSnap)
+{
+ *aSnap = false;
+ nsRect untransformedVisible;
+ if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) {
+ return nsRegion();
+ }
+
+ const Matrix4x4& matrix = GetTransform();
+
+ nsRegion result;
+ Matrix matrix2d;
+ bool tmpSnap;
+ if (matrix.Is2D(&matrix2d) &&
+ matrix2d.PreservesAxisAlignedRectangles() &&
+ mStoredList.GetOpaqueRegion(aBuilder, &tmpSnap).Contains(untransformedVisible)) {
+ result = mVisibleRect.Intersect(GetBounds(aBuilder, &tmpSnap));
+ }
+ return result;
+}
+
+/* The transform is uniform if it fills the entire bounding rect and the
+ * wrapped list is uniform. See GetOpaqueRegion for discussion of why this
+ * works.
+ */
+Maybe<nscolor>
+nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder)
+{
+ nsRect untransformedVisible;
+ if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) {
+ return Nothing();
+ }
+ const Matrix4x4& matrix = GetTransform();
+
+ Matrix matrix2d;
+ if (matrix.Is2D(&matrix2d) &&
+ matrix2d.PreservesAxisAlignedRectangles() &&
+ mStoredList.GetVisibleRect().Contains(untransformedVisible)) {
+ return mStoredList.IsUniform(aBuilder);
+ }
+
+ return Nothing();
+}
+
+/* If UNIFIED_CONTINUATIONS is defined, we can merge two display lists that
+ * share the same underlying content. Otherwise, doing so results in graphical
+ * glitches.
+ */
+#ifndef UNIFIED_CONTINUATIONS
+
+bool
+nsDisplayTransform::TryMerge(nsDisplayItem *aItem)
+{
+ return false;
+}
+
+#else
+
+bool
+nsDisplayTransform::TryMerge(nsDisplayItem *aItem)
+{
+ NS_PRECONDITION(aItem, "Why did you try merging with a null item?");
+
+ /* Make sure that we're dealing with two transforms. */
+ if (aItem->GetType() != TYPE_TRANSFORM)
+ return false;
+
+ /* Check to see that both frames are part of the same content. */
+ if (aItem->Frame()->GetContent() != mFrame->GetContent())
+ return false;
+
+ if (aItem->GetClip() != GetClip())
+ return false;
+
+ if (aItem->ScrollClip() != ScrollClip())
+ return false;
+
+ /* Now, move everything over to this frame and signal that
+ * we merged things!
+ */
+ mStoredList.MergeFromTrackingMergedFrames(&static_cast<nsDisplayTransform*>(aItem)->mStoredList);
+ return true;
+}
+
+#endif
+
+/* TransformRect takes in as parameters a rectangle (in app space) and returns
+ * the smallest rectangle (in app space) containing the transformed image of
+ * that rectangle. That is, it takes the four corners of the rectangle,
+ * transforms them according to the matrix associated with the specified frame,
+ * then returns the smallest rectangle containing the four transformed points.
+ *
+ * @param aUntransformedBounds The rectangle (in app units) to transform.
+ * @param aFrame The frame whose transformation should be applied.
+ * @param aOrigin The delta from the frame origin to the coordinate space origin
+ * @param aBoundsOverride (optional) Force the frame bounds to be the
+ * specified bounds.
+ * @return The smallest rectangle containing the image of the transformed
+ * rectangle.
+ */
+nsRect nsDisplayTransform::TransformRect(const nsRect &aUntransformedBounds,
+ const nsIFrame* aFrame,
+ const nsRect* aBoundsOverride)
+{
+ NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
+
+ float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ uint32_t flags = INCLUDE_PERSPECTIVE|OFFSET_BY_ORIGIN|INCLUDE_PRESERVE3D_ANCESTORS;
+ return nsLayoutUtils::MatrixTransformRect
+ (aUntransformedBounds,
+ GetResultingTransformMatrix(aFrame, nsPoint(0, 0), factor, flags, aBoundsOverride),
+ factor);
+}
+
+bool nsDisplayTransform::UntransformRect(const nsRect &aTransformedBounds,
+ const nsRect &aChildBounds,
+ const nsIFrame* aFrame,
+ nsRect *aOutRect)
+{
+ NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
+
+ float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ uint32_t flags = INCLUDE_PERSPECTIVE|OFFSET_BY_ORIGIN|INCLUDE_PRESERVE3D_ANCESTORS;
+
+ Matrix4x4 transform = GetResultingTransformMatrix(aFrame, nsPoint(0, 0), factor, flags);
+ if (transform.IsSingular()) {
+ return false;
+ }
+
+ RectDouble result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.y, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.width, factor),
+ NSAppUnitsToFloatPixels(aTransformedBounds.height, factor));
+
+ RectDouble childGfxBounds(NSAppUnitsToFloatPixels(aChildBounds.x, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.y, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.width, factor),
+ NSAppUnitsToFloatPixels(aChildBounds.height, factor));
+
+ result = transform.Inverse().ProjectRectBounds(result, childGfxBounds);
+ *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);
+ return true;
+}
+
+bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
+ nsRect *aOutRect)
+{
+ const Matrix4x4& matrix = GetTransform();
+ if (matrix.IsSingular())
+ return false;
+
+ // GetTransform always operates in dev pixels.
+ float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ RectDouble result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor),
+ NSAppUnitsToFloatPixels(mVisibleRect.y, factor),
+ NSAppUnitsToFloatPixels(mVisibleRect.width, factor),
+ NSAppUnitsToFloatPixels(mVisibleRect.height, factor));
+
+ bool snap;
+ nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap);
+ RectDouble childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+ NSAppUnitsToFloatPixels(childBounds.y, factor),
+ NSAppUnitsToFloatPixels(childBounds.width, factor),
+ NSAppUnitsToFloatPixels(childBounds.height, factor));
+
+ /* We want to untransform the matrix, so invert the transformation first! */
+ result = matrix.Inverse().ProjectRectBounds(result, childGfxBounds);
+
+ *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);
+
+ return true;
+}
+
+void
+nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream)
+{
+ AppendToString(aStream, GetTransform());
+ if (IsTransformSeparator()) {
+ aStream << " transform-separator";
+ }
+ if (IsLeafOf3DContext()) {
+ aStream << " 3d-context-leaf";
+ }
+ if (mFrame->Extend3DContext()) {
+ aStream << " extends-3d-context";
+ }
+ if (mFrame->Combines3DTransformWithAncestors()) {
+ aStream << " combines-3d-with-ancestors";
+ }
+}
+
+nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aTransformFrame,
+ nsIFrame* aPerspectiveFrame,
+ nsDisplayList* aList)
+ : nsDisplayItem(aBuilder, aPerspectiveFrame)
+ , mList(aBuilder, aPerspectiveFrame, aList)
+ , mTransformFrame(aTransformFrame)
+ , mIndex(aBuilder->AllocatePerspectiveItemIndex())
+{
+ MOZ_ASSERT(mList.GetChildren()->Count() == 1);
+ MOZ_ASSERT(mList.GetChildren()->GetTop()->GetType() == TYPE_TRANSFORM);
+}
+
+already_AddRefed<Layer>
+nsDisplayPerspective::BuildLayer(nsDisplayListBuilder *aBuilder,
+ LayerManager *aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ Matrix4x4 perspectiveMatrix;
+ DebugOnly<bool> hasPerspective =
+ nsDisplayTransform::ComputePerspectiveMatrix(mTransformFrame, appUnitsPerPixel,
+ perspectiveMatrix);
+ MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?");
+
+ /*
+ * ClipListToRange can remove our child after we were created.
+ */
+ if (!mList.GetChildren()->GetTop()) {
+ return nullptr;
+ }
+
+ /*
+ * The resulting matrix is still in the coordinate space of the transformed
+ * frame. Append a translation to the reference frame coordinates.
+ */
+ nsDisplayTransform* transform =
+ static_cast<nsDisplayTransform*>(mList.GetChildren()->GetTop());
+
+ Point3D newOrigin =
+ Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, appUnitsPerPixel),
+ NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, appUnitsPerPixel),
+ 0.0f);
+ Point3D roundedOrigin(NS_round(newOrigin.x),
+ NS_round(newOrigin.y),
+ 0);
+
+ perspectiveMatrix.PostTranslate(roundedOrigin);
+
+ RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mList.GetChildren(),
+ aContainerParameters, &perspectiveMatrix, 0);
+
+ if (!container) {
+ return nullptr;
+ }
+
+ // Sort of a lie, but we want to pretend that the perspective layer extends a 3d context
+ // so that it gets its transform combined with children. Might need a better name that reflects
+ // this use case and isn't specific to preserve-3d.
+ container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_EXTEND_3D_CONTEXT);
+ container->SetTransformIsPerspective(true);
+
+ return container.forget();
+}
+
+LayerState
+nsDisplayPerspective::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ return LAYER_ACTIVE_FORCE;
+}
+
+int32_t
+nsDisplayPerspective::ZIndex() const
+{
+ return ZIndexForFrame(mTransformFrame);
+}
+
+nsDisplayItemGeometry*
+nsCharClipDisplayItem::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsCharClipGeometry(this, aBuilder);
+}
+
+void
+nsCharClipDisplayItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ const nsCharClipGeometry* geometry = static_cast<const nsCharClipGeometry*>(aGeometry);
+
+ bool snap;
+ nsRect newRect = geometry->mBounds;
+ nsRect oldRect = GetBounds(aBuilder, &snap);
+ if (mVisIStartEdge != geometry->mVisIStartEdge ||
+ mVisIEndEdge != geometry->mVisIEndEdge ||
+ !oldRect.IsEqualInterior(newRect) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
+ aInvalidRegion->Or(oldRect, newRect);
+ }
+}
+
+nsDisplaySVGEffects::nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ bool aHandleOpacity,
+ const DisplayItemScrollClip* aScrollClip)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+ , mEffectsBounds(aFrame->GetVisualOverflowRectRelativeToSelf())
+ , mHandleOpacity(aHandleOpacity)
+{
+ MOZ_COUNT_CTOR(nsDisplaySVGEffects);
+}
+
+nsDisplaySVGEffects::nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ bool aHandleOpacity)
+ : nsDisplayWrapList(aBuilder, aFrame, aList)
+ , mEffectsBounds(aFrame->GetVisualOverflowRectRelativeToSelf())
+ , mHandleOpacity(aHandleOpacity)
+{
+ MOZ_COUNT_CTOR(nsDisplaySVGEffects);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplaySVGEffects::~nsDisplaySVGEffects()
+{
+ MOZ_COUNT_DTOR(nsDisplaySVGEffects);
+}
+#endif
+
+nsRegion nsDisplaySVGEffects::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap)
+{
+ *aSnap = false;
+ return nsRegion();
+}
+
+void
+nsDisplaySVGEffects::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2);
+ if (nsSVGIntegrationUtils::HitTestFrameForEffects(mFrame,
+ rectCenter - ToReferenceFrame())) {
+ mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+ }
+}
+
+gfxRect
+nsDisplaySVGEffects::BBoxInUserSpace() const
+{
+ return nsSVGUtils::GetBBox(mFrame);
+}
+
+gfxPoint
+nsDisplaySVGEffects::UserSpaceOffset() const
+{
+ return nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame);
+}
+
+void
+nsDisplaySVGEffects::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ const nsDisplaySVGEffectGeometry* geometry =
+ static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry);
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() ||
+ geometry->mUserSpaceOffset != UserSpaceOffset() ||
+ !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) {
+ // Filter and mask output can depend on the location of the frame's user
+ // space and on the frame's BBox. We need to invalidate if either of these
+ // change relative to the reference frame.
+ // Invalidations from our inactive layer manager are not enough to catch
+ // some of these cases because filters can produce output even if there's
+ // nothing in the filter input.
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ }
+}
+
+bool nsDisplaySVGEffects::ValidateSVGFrame()
+{
+ const nsIContent* content = mFrame->GetContent();
+ bool hasSVGLayout = (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
+ if (hasSVGLayout) {
+ nsISVGChildFrame *svgChildFrame = do_QueryFrame(mFrame);
+ if (!svgChildFrame || !mFrame->GetContent()->IsSVGElement()) {
+ NS_ASSERTION(false, "why?");
+ return false;
+ }
+ if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
+ return false; // The SVG spec says not to draw filters for this
+ }
+ }
+
+ return true;
+}
+
+static IntRect
+ComputeClipExtsInDeviceSpace(gfxContext& aCtx)
+{
+ gfxContextMatrixAutoSaveRestore matRestore(&aCtx);
+
+ // Get the clip extents in device space.
+ aCtx.SetMatrix(gfxMatrix());
+ gfxRect clippedFrameSurfaceRect = aCtx.GetClipExtents();
+ clippedFrameSurfaceRect.RoundOut();
+
+ IntRect result;
+ ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
+ return mozilla::gfx::Factory::CheckSurfaceSize(result.Size()) ? result
+ : IntRect();
+}
+
+typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
+
+static nsPoint
+ComputeOffsetToUserSpace(const PaintFramesParams& aParams)
+{
+ nsIFrame* frame = aParams.frame;
+ nsPoint offsetToBoundingBox = aParams.builder->ToReferenceFrame(frame) -
+ nsSVGIntegrationUtils::GetOffsetToBoundingBox(frame);
+ if (!frame->IsFrameOfType(nsIFrame::eSVG)) {
+ // Snap the offset if the reference frame is not a SVG frame, since other
+ // frames will be snapped to pixel when rendering.
+ offsetToBoundingBox = nsPoint(
+ frame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x),
+ frame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y));
+ }
+
+ // After applying only "offsetToBoundingBox", aParams.ctx would have its
+ // origin at the top left corner of frame's bounding box (over all
+ // continuations).
+ // However, SVG painting needs the origin to be located at the origin of the
+ // SVG frame's "user space", i.e. the space in which, for example, the
+ // frame's BBox lives.
+ // SVG geometry frames and foreignObject frames apply their own offsets, so
+ // their position is relative to their user space. So for these frame types,
+ // if we want aCtx to be in user space, we first need to subtract the
+ // frame's position so that SVG painting can later add it again and the
+ // frame is painted in the right place.
+ gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(frame);
+ nsPoint toUserSpace =
+ nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
+ nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
+
+ return (offsetToBoundingBox - toUserSpace);
+}
+
+static void
+ComputeMaskGeometry(PaintFramesParams& aParams)
+{
+ // Properties are added lazily and may have been removed by a restyle, so
+ // make sure all applicable ones are set again.
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame);
+
+ const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
+
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+
+ if (maskFrames.Length() == 0) {
+ return;
+ }
+
+ gfxContext& ctx = aParams.ctx;
+ nsIFrame* frame = aParams.frame;
+
+ nsPoint offsetToUserSpace = ComputeOffsetToUserSpace(aParams);
+ gfxPoint devPixelOffsetToUserSpace =
+ nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
+ frame->PresContext()->AppUnitsPerDevPixel());
+
+ gfxContextMatrixAutoSaveRestore matSR(&ctx);
+ ctx.SetMatrix(ctx.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+
+ // Convert boaderArea and dirtyRect to user space.
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+ nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace;
+ nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace;
+
+ // Union all mask layer rectangles in user space.
+ gfxRect maskInUserSpace;
+ for (size_t i = 0; i < maskFrames.Length() ; i++) {
+ nsSVGMaskFrame* maskFrame = maskFrames[i];
+ gfxRect currentMaskSurfaceRect;
+
+ if (maskFrame) {
+ currentMaskSurfaceRect = maskFrame->GetMaskArea(aParams.frame);
+ } else {
+ nsCSSRendering::ImageLayerClipState clipState;
+ nsCSSRendering::GetImageLayerClip(svgReset->mMask.mLayers[i],
+ frame,
+ *frame->StyleBorder(),
+ userSpaceBorderArea,
+ userSpaceDirtyRect,
+ false, /* aWillPaintBorder */
+ appUnitsPerDevPixel,
+ &clipState);
+ currentMaskSurfaceRect = clipState.mDirtyRectGfx;
+ }
+
+ maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect);
+ }
+
+ ctx.Save();
+
+ if (!maskInUserSpace.IsEmpty()) {
+ ctx.Clip(maskInUserSpace);
+ }
+
+ IntRect result = ComputeClipExtsInDeviceSpace(ctx);
+ ctx.Restore();
+
+ aParams.maskRect = result;
+}
+
+nsDisplayMask::nsDisplayMask(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ bool aHandleOpacity,
+ const DisplayItemScrollClip* aScrollClip)
+ : nsDisplaySVGEffects(aBuilder, aFrame, aList, aHandleOpacity, aScrollClip)
+{
+ MOZ_COUNT_CTOR(nsDisplayMask);
+
+ nsPresContext* presContext = mFrame->PresContext();
+ uint32_t flags = aBuilder->GetBackgroundPaintFlags() |
+ nsCSSRendering::PAINTBG_MASK_IMAGE;
+ const nsStyleSVGReset *svgReset = aFrame->StyleSVGReset();
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) {
+ bool isTransformedFixed;
+ nsBackgroundLayerState state =
+ nsCSSRendering::PrepareImageLayer(presContext, aFrame, flags,
+ mFrame->GetRectRelativeToSelf(),
+ mFrame->GetRectRelativeToSelf(),
+ svgReset->mMask.mLayers[i],
+ &isTransformedFixed);
+ mDestRects.AppendElement(state.mDestArea);
+ }
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayMask::~nsDisplayMask()
+{
+ MOZ_COUNT_DTOR(nsDisplayMask);
+}
+#endif
+
+bool nsDisplayMask::TryMerge(nsDisplayItem* aItem)
+{
+ if (aItem->GetType() != TYPE_MASK)
+ return false;
+
+ // items for the same content element should be merged into a single
+ // compositing group
+ // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplaySVGEffects
+ if (aItem->Frame()->GetContent() != mFrame->GetContent()) {
+ return false;
+ }
+ if (aItem->GetClip() != GetClip()) {
+ return false;
+ }
+ if (aItem->ScrollClip() != ScrollClip()) {
+ return false;
+ }
+
+ // Do not merge if mFrame has mask. Continuation frames should apply mask
+ // independently(just like nsDisplayBackgroundImage).
+ const nsStyleSVGReset *style = mFrame->StyleSVGReset();
+ if (style->mMask.HasLayerWithImage()) {
+ return false;
+ }
+
+ nsDisplayMask* other = static_cast<nsDisplayMask*>(aItem);
+ MergeFromTrackingMergedFrames(other);
+ mEffectsBounds.UnionRect(mEffectsBounds,
+ other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+
+ return true;
+}
+
+already_AddRefed<Layer>
+nsDisplayMask::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ if (!ValidateSVGFrame()) {
+ return nullptr;
+ }
+
+ if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) {
+ return nullptr;
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+
+ bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
+ effectProperties.GetClipPathFrame(&isOK);
+
+ if (!isOK) {
+ return nullptr;
+ }
+
+ RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ aContainerParameters, nullptr);
+
+ return container.forget();
+}
+
+bool
+nsDisplayMask::PaintMask(nsDisplayListBuilder* aBuilder,
+ gfxContext* aMaskContext)
+{
+ MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);
+
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ nsSVGIntegrationUtils::PaintFramesParams params(*aMaskContext,
+ mFrame, mVisibleRect,
+ borderArea, aBuilder,
+ nullptr,
+ mHandleOpacity);
+ ComputeMaskGeometry(params);
+ image::DrawResult result = nsSVGIntegrationUtils::PaintMask(params);
+
+ nsDisplayMaskGeometry::UpdateDrawResult(this, result);
+ return (result == image::DrawResult::SUCCESS) ? true : false;
+}
+
+LayerState
+nsDisplayMask::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ if (ShouldPaintOnMaskLayer(aManager)) {
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters,
+ mList, GetAnimatedGeometryRoot());
+ }
+
+ return LAYER_SVG_EFFECTS;
+}
+
+bool nsDisplayMask::ShouldPaintOnMaskLayer(LayerManager* aManager)
+{
+ if (!aManager->IsCompositingCheap()) {
+ return false;
+ }
+
+ nsSVGUtils::MaskUsage maskUsage;
+ nsSVGUtils::DetermineMaskUsage(mFrame, mHandleOpacity, maskUsage);
+
+ if (!maskUsage.shouldGenerateMaskLayer ||
+ maskUsage.opacity != 1.0 || maskUsage.shouldApplyClipPath ||
+ maskUsage.shouldApplyBasicShape ||
+ maskUsage.shouldGenerateClipMaskLayer) {
+ return false;
+ }
+
+ if (!nsSVGIntegrationUtils::IsMaskResourceReady(mFrame)) {
+ return false;
+ }
+
+ // XXX temporary disable drawing SVG mask onto mask layer before bug 1313877
+ // been fixed.
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+ for (size_t i = 0; i < maskFrames.Length() ; i++) {
+ nsSVGMaskFrame *maskFrame = maskFrames[i];
+ if (maskFrame) {
+ return false; // Found SVG mask.
+ }
+ }
+
+ if (gfxPrefs::DrawMaskLayer()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsDisplayMask::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion)
+{
+ // Our children may be made translucent or arbitrarily deformed so we should
+ // not allow them to subtract area from aVisibleRegion.
+ nsRegion childrenVisible(mVisibleRect);
+ nsRect r = mVisibleRect.Intersect(mList.GetBounds(aBuilder));
+ mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
+ return true;
+}
+
+void
+nsDisplayMask::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ nsDisplaySVGEffects::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+
+ const nsDisplayMaskGeometry* geometry =
+ static_cast<const nsDisplayMaskGeometry*>(aGeometry);
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+
+ if (mDestRects.Length() != geometry->mDestRects.Length()) {
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ } else {
+ for (size_t i = 0; i < mDestRects.Length(); i++) {
+ if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) {
+ aInvalidRegion->Or(bounds, geometry->mBounds);
+ break;
+ }
+ }
+ }
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ const nsStyleSVGReset *svgReset = mFrame->StyleSVGReset();
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) {
+ const nsStyleImage& image = svgReset->mMask.mLayers[i].mImage;
+ if (image.GetType() == eStyleImageType_Image ) {
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ break;
+ }
+ }
+ }
+}
+
+void
+nsDisplayMask::PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ LayerManager* aManager)
+{
+ MOZ_ASSERT(!ShouldPaintOnMaskLayer(aManager));
+
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ nsSVGIntegrationUtils::PaintFramesParams params(*aCtx->ThebesContext(),
+ mFrame, mVisibleRect,
+ borderArea, aBuilder,
+ aManager,
+ mHandleOpacity);
+
+ // Clip the drawing target by mVisibleRect, which contains the visible
+ // region of the target frame and its out-of-flow and inflow descendants.
+ gfxContext* context = aCtx->ThebesContext();
+
+ Rect bounds =
+ NSRectToRect(mVisibleRect, mFrame->PresContext()->AppUnitsPerDevPixel());
+ bounds.RoundOut();
+ context->Clip(bounds);
+
+ ComputeMaskGeometry(params);
+
+ image::DrawResult result =
+ nsSVGIntegrationUtils::PaintMaskAndClipPath(params);
+
+ context->PopClip();
+
+ nsDisplayMaskGeometry::UpdateDrawResult(this, result);
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void
+nsDisplayMask::PrintEffects(nsACString& aTo)
+{
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ bool isOK = true;
+ nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
+ bool first = true;
+ aTo += " effects=(";
+ if (mFrame->StyleEffects()->mOpacity != 1.0f && mHandleOpacity) {
+ first = false;
+ aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity);
+ }
+ if (clipPathFrame) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += nsPrintfCString("clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial");
+ first = false;
+ }
+ const nsStyleSVGReset *style = mFrame->StyleSVGReset();
+ if (style->HasClipPath() && !clipPathFrame) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "clip(basic-shape)";
+ first = false;
+ }
+
+ if (effectProperties.GetFirstMaskFrame()) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "mask";
+ }
+ aTo += ")";
+}
+#endif
+
+nsDisplayFilter::nsDisplayFilter(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ bool aHandleOpacity)
+ : nsDisplaySVGEffects(aBuilder, aFrame, aList, aHandleOpacity)
+{
+ MOZ_COUNT_CTOR(nsDisplayFilter);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayFilter::~nsDisplayFilter()
+{
+ MOZ_COUNT_DTOR(nsDisplayFilter);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplayFilter::BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ if (!ValidateSVGFrame()) {
+ return nullptr;
+ }
+
+ if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) {
+ return nullptr;
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+
+ ContainerLayerParameters newContainerParameters = aContainerParameters;
+ if (effectProperties.HasValidFilter()) {
+ newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
+ }
+
+ RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
+ BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+ newContainerParameters, nullptr);
+
+ return container.forget();
+}
+
+bool nsDisplayFilter::TryMerge(nsDisplayItem* aItem)
+{
+ if (aItem->GetType() != TYPE_FILTER) {
+ return false;
+ }
+
+ // items for the same content element should be merged into a single
+ // compositing group.
+ // aItem->Frame() returns non-null because it's nsDisplayFilter
+ if (aItem->Frame()->GetContent() != mFrame->GetContent()) {
+ return false;
+ }
+ if (aItem->GetClip() != GetClip()) {
+ return false;
+ }
+ if (aItem->ScrollClip() != ScrollClip()) {
+ return false;
+ }
+
+ nsDisplayFilter* other = static_cast<nsDisplayFilter*>(aItem);
+ MergeFromTrackingMergedFrames(other);
+ mEffectsBounds.UnionRect(mEffectsBounds,
+ other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+
+ return true;
+}
+
+LayerState
+nsDisplayFilter::GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+{
+ return LAYER_SVG_EFFECTS;
+}
+
+bool nsDisplayFilter::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion)
+{
+ nsPoint offset = ToReferenceFrame();
+ nsRect dirtyRect =
+ nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(mFrame,
+ mVisibleRect - offset) +
+ offset;
+
+ // Our children may be made translucent or arbitrarily deformed so we should
+ // not allow them to subtract area from aVisibleRegion.
+ nsRegion childrenVisible(dirtyRect);
+ nsRect r = dirtyRect.Intersect(mList.GetBounds(aBuilder));
+ mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
+ return true;
+}
+
+void
+nsDisplayFilter::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ nsDisplaySVGEffects::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+
+ const nsDisplayFilterGeometry* geometry =
+ static_cast<const nsDisplayFilterGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+ aInvalidRegion->Or(*aInvalidRegion, bounds);
+ }
+}
+
+void
+nsDisplayFilter::PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ LayerManager* aManager)
+{
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ nsSVGIntegrationUtils::PaintFramesParams params(*aCtx->ThebesContext(),
+ mFrame, mVisibleRect,
+ borderArea, aBuilder,
+ aManager,
+ mHandleOpacity);
+
+ image::DrawResult result = nsSVGIntegrationUtils::PaintFilter(params);
+ nsDisplayFilterGeometry::UpdateDrawResult(this, result);
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void
+nsDisplayFilter::PrintEffects(nsACString& aTo)
+{
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ bool first = true;
+ aTo += " effects=(";
+ if (mFrame->StyleEffects()->mOpacity != 1.0f && mHandleOpacity) {
+ first = false;
+ aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity);
+ }
+ if (effectProperties.HasValidFilter()) {
+ if (!first) {
+ aTo += ", ";
+ }
+ aTo += "filter";
+ }
+ aTo += ")";
+}
+#endif
+
+namespace mozilla {
+
+uint32_t PaintTelemetry::sPaintLevel = 0;
+uint32_t PaintTelemetry::sMetricLevel = 0;
+EnumeratedArray<PaintTelemetry::Metric,
+ PaintTelemetry::Metric::COUNT,
+ double> PaintTelemetry::sMetrics;
+
+PaintTelemetry::AutoRecordPaint::AutoRecordPaint()
+{
+ // Don't record nested paints.
+ if (sPaintLevel++ > 0) {
+ return;
+ }
+
+ // Reset metrics for a new paint.
+ for (auto& metric : sMetrics) {
+ metric = 0.0;
+ }
+ mStart = TimeStamp::Now();
+}
+
+PaintTelemetry::AutoRecordPaint::~AutoRecordPaint()
+{
+ MOZ_ASSERT(sPaintLevel != 0);
+ if (--sPaintLevel > 0) {
+ return;
+ }
+
+ // If we're in multi-process mode, don't include paint times for the parent
+ // process.
+ if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) {
+ return;
+ }
+
+ double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds();
+
+ // Record the total time.
+ Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME, static_cast<uint32_t>(totalMs));
+
+ // If the total time was >= 16ms, then it's likely we missed a frame due to
+ // painting. In this case we'll gather some detailed metrics below.
+ if (totalMs <= 16.0) {
+ return;
+ }
+
+ auto record = [=](const char* aKey, double aDurationMs) -> void {
+ MOZ_ASSERT(aDurationMs <= totalMs);
+
+ uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0);
+
+ nsDependentCString key(aKey);
+ Telemetry::Accumulate(Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT, key, amount);
+ };
+
+ double dlMs = sMetrics[Metric::DisplayList];
+ double flbMs = sMetrics[Metric::Layerization];
+ double rMs = sMetrics[Metric::Rasterization];
+
+ // Record all permutations since aggregation makes it difficult to
+ // correlate. For example we can't derive "flb+r" from "dl" because we
+ // don't know the total time associated with a bucket entry. So we just
+ // play it safe and include everything. We can however derive "other" time
+ // from the final permutation.
+ record("dl", dlMs);
+ record("flb", flbMs);
+ record("r", rMs);
+ record("dl,flb", dlMs + flbMs);
+ record("dl,r", dlMs + rMs);
+ record("flb,r", flbMs + rMs);
+ record("dl,flb,r", dlMs + flbMs + rMs);
+}
+
+PaintTelemetry::AutoRecord::AutoRecord(Metric aMetric)
+ : mMetric(aMetric)
+{
+ // Don't double-record anything nested.
+ if (sMetricLevel++ > 0) {
+ return;
+ }
+
+ // Don't record inside nested paints, or outside of paints.
+ if (sPaintLevel != 1) {
+ return;
+ }
+
+ mStart = TimeStamp::Now();
+}
+
+PaintTelemetry::AutoRecord::~AutoRecord()
+{
+ MOZ_ASSERT(sMetricLevel != 0);
+
+ sMetricLevel--;
+ if (mStart.IsNull()) {
+ return;
+ }
+
+ sMetrics[mMetric] += (TimeStamp::Now() - mStart).ToMilliseconds();
+}
+
+} // namespace mozilla
diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h
new file mode 100644
index 000000000..df584b489
--- /dev/null
+++ b/layout/base/nsDisplayList.h
@@ -0,0 +1,4550 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/*
+ * structures that represent things to be painted (ordered in z-order),
+ * used during painting and hit testing
+ */
+
+#ifndef NSDISPLAYLIST_H_
+#define NSDISPLAYLIST_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsContainerFrame.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "plarena.h"
+#include "nsRegion.h"
+#include "nsDisplayListInvalidation.h"
+#include "nsRenderingContext.h"
+#include "DisplayListClipState.h"
+#include "LayerState.h"
+#include "FrameMetrics.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/gfx/UserData.h"
+
+#include <stdint.h>
+#include "nsTHashtable.h"
+
+#include <stdlib.h>
+#include <algorithm>
+
+class nsIContent;
+class nsRenderingContext;
+class nsDisplayList;
+class nsDisplayTableItem;
+class nsISelection;
+class nsIScrollableFrame;
+class nsDisplayLayerEventRegions;
+class nsDisplayScrollInfoLayer;
+class nsCaret;
+
+namespace mozilla {
+class FrameLayerBuilder;
+class DisplayItemScrollClip;
+namespace layers {
+class Layer;
+class ImageLayer;
+class ImageContainer;
+} // namespace layers
+} // namespace mozilla
+
+// A set of blend modes, that never includes OP_OVER (since it's
+// considered the default, rather than a specific blend mode).
+typedef mozilla::EnumSet<mozilla::gfx::CompositionOp> BlendModeSet;
+
+/*
+ * An nsIFrame can have many different visual parts. For example an image frame
+ * can have a background, border, and outline, the image itself, and a
+ * translucent selection overlay. In general these parts can be drawn at
+ * discontiguous z-levels; see CSS2.1 appendix E:
+ * http://www.w3.org/TR/CSS21/zindex.html
+ *
+ * We construct a display list for a frame tree that contains one item
+ * for each visual part. The display list is itself a tree since some items
+ * are containers for other items; however, its structure does not match
+ * the structure of its source frame tree. The display list items are sorted
+ * by z-order. A display list can be used to paint the frames, to determine
+ * which frame is the target of a mouse event, and to determine what areas
+ * need to be repainted when scrolling. The display lists built for each task
+ * may be different for efficiency; in particular some frames need special
+ * display list items only for event handling, and do not create these items
+ * when the display list will be used for painting (the common case). For
+ * example, when painting we avoid creating nsDisplayBackground items for
+ * frames that don't display a visible background, but for event handling
+ * we need those backgrounds because they are not transparent to events.
+ *
+ * We could avoid constructing an explicit display list by traversing the
+ * frame tree multiple times in clever ways. However, reifying the display list
+ * reduces code complexity and reduces the number of times each frame must be
+ * traversed to one, which seems to be good for performance. It also means
+ * we can share code for painting, event handling and scroll analysis.
+ *
+ * Display lists are short-lived; content and frame trees cannot change
+ * between a display list being created and destroyed. Display lists should
+ * not be created during reflow because the frame tree may be in an
+ * inconsistent state (e.g., a frame's stored overflow-area may not include
+ * the bounds of all its children). However, it should be fine to create
+ * a display list while a reflow is pending, before it starts.
+ *
+ * A display list covers the "extended" frame tree; the display list for a frame
+ * tree containing FRAME/IFRAME elements can include frames from the subdocuments.
+ *
+ * Display item's coordinates are relative to their nearest reference frame ancestor.
+ * Both the display root and any frame with a transform act as a reference frame
+ * for their frame subtrees.
+ */
+
+// All types are defined in nsDisplayItemTypes.h
+#define NS_DISPLAY_DECL_NAME(n, e) \
+ virtual const char* Name() override { return n; } \
+ virtual Type GetType() override { return e; }
+
+
+/**
+ * Represents a frame that is considered to have (or will have) "animated geometry"
+ * for itself and descendant frames.
+ *
+ * For example the scrolled frames of scrollframes which are actively being scrolled
+ * fall into this category. Frames with certain CSS properties that are being animated
+ * (e.g. 'left'/'top' etc) are also placed in this category. Frames with different
+ * active geometry roots are in different PaintedLayers, so that we can animate the
+ * geometry root by changing its transform (either on the main thread or in the
+ * compositor).
+ *
+ * nsDisplayListBuilder constructs a tree of these (for fast traversals) and assigns
+ * one for each display item.
+ *
+ * The animated geometry root for a display item is required to be a descendant (or
+ * equal to) the item's ReferenceFrame(), which means that we will fall back to
+ * returning aItem->ReferenceFrame() when we can't find another animated geometry root.
+ *
+ * The animated geometry root isn't strongly defined for a frame as transforms and
+ * background-attachment:fixed can cause it to vary between display items for a given
+ * frame.
+ */
+struct AnimatedGeometryRoot
+{
+ AnimatedGeometryRoot(nsIFrame* aFrame, AnimatedGeometryRoot* aParent)
+ : mFrame(aFrame)
+ , mParentAGR(aParent)
+ {}
+
+ operator nsIFrame*() { return mFrame; }
+
+ nsIFrame* operator ->() const { return mFrame; }
+
+ void* operator new(size_t aSize,
+ nsDisplayListBuilder* aBuilder);
+
+ nsIFrame* mFrame;
+ AnimatedGeometryRoot* mParentAGR;
+};
+
+enum class nsDisplayListBuilderMode : uint8_t {
+ PAINTING,
+ EVENT_DELIVERY,
+ PLUGIN_GEOMETRY,
+ FRAME_VISIBILITY,
+ TRANSFORM_COMPUTATION,
+ GENERATE_GLYPH,
+ PAINTING_SELECTION_BACKGROUND
+};
+
+/**
+ * This manages a display list and is passed as a parameter to
+ * nsIFrame::BuildDisplayList.
+ * It contains the parameters that don't change from frame to frame and manages
+ * the display list memory using a PLArena. It also establishes the reference
+ * coordinate system for all display list items. Some of the parameters are
+ * available from the prescontext/presshell, but we copy them into the builder
+ * for faster/more convenient access.
+ */
+class nsDisplayListBuilder {
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion;
+
+ /**
+ * This manages status of a 3d context to collect visible rects of
+ * descendants and passing a dirty rect.
+ *
+ * Since some transforms maybe singular, passing visible rects or
+ * the dirty rect level by level from parent to children may get a
+ * wrong result, being different from the result of appling with
+ * effective transform directly.
+ *
+ * nsFrame::BuildDisplayListForStackingContext() uses
+ * AutoPreserves3DContext to install an instance on the builder.
+ *
+ * \see AutoAccumulateTransform, AutoAccumulateRect,
+ * AutoPreserves3DContext, Accumulate, GetCurrentTransform,
+ * StartRoot.
+ */
+ class Preserves3DContext {
+ public:
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+ Preserves3DContext()
+ : mAccumulatedRectLevels(0)
+ {}
+ Preserves3DContext(const Preserves3DContext &aOther)
+ : mAccumulatedTransform()
+ , mAccumulatedRect()
+ , mAccumulatedRectLevels(0)
+ , mDirtyRect(aOther.mDirtyRect) {}
+
+ // Accmulate transforms of ancestors on the preserves-3d chain.
+ Matrix4x4 mAccumulatedTransform;
+ // Accmulate visible rect of descendants in the preserves-3d context.
+ nsRect mAccumulatedRect;
+ // How far this frame is from the root of the current 3d context.
+ int mAccumulatedRectLevels;
+ nsRect mDirtyRect;
+ };
+
+public:
+ typedef mozilla::FrameLayerBuilder FrameLayerBuilder;
+ typedef mozilla::DisplayItemClip DisplayItemClip;
+ typedef mozilla::DisplayListClipState DisplayListClipState;
+ typedef mozilla::DisplayItemScrollClip DisplayItemScrollClip;
+ typedef nsIWidget::ThemeGeometry ThemeGeometry;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::FrameMetrics::ViewID ViewID;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+ /**
+ * @param aReferenceFrame the frame at the root of the subtree; its origin
+ * is the origin of the reference coordinate system for this display list
+ * @param aMode encodes what the builder is being used for.
+ * @param aBuildCaret whether or not we should include the caret in any
+ * display lists that we make.
+ */
+ nsDisplayListBuilder(nsIFrame* aReferenceFrame,
+ nsDisplayListBuilderMode aMode,
+ bool aBuildCaret);
+ ~nsDisplayListBuilder();
+
+ void SetWillComputePluginGeometry(bool aWillComputePluginGeometry)
+ {
+ mWillComputePluginGeometry = aWillComputePluginGeometry;
+ }
+ void SetForPluginGeometry()
+ {
+ NS_ASSERTION(mMode == nsDisplayListBuilderMode::PAINTING, "Can only switch from PAINTING to PLUGIN_GEOMETRY");
+ NS_ASSERTION(mWillComputePluginGeometry, "Should have signalled this in advance");
+ mMode = nsDisplayListBuilderMode::PLUGIN_GEOMETRY;
+ }
+
+ mozilla::layers::LayerManager* GetWidgetLayerManager(nsView** aView = nullptr);
+
+ /**
+ * @return true if the display is being built in order to determine which
+ * frame is under the mouse position.
+ */
+ bool IsForEventDelivery()
+ {
+ return mMode == nsDisplayListBuilderMode::EVENT_DELIVERY;
+ }
+
+ /**
+ * Be careful with this. The display list will be built in PAINTING mode
+ * first and then switched to PLUGIN_GEOMETRY before a second call to
+ * ComputeVisibility.
+ * @return true if the display list is being built to compute geometry
+ * for plugins.
+ */
+ bool IsForPluginGeometry()
+ {
+ return mMode == nsDisplayListBuilderMode::PLUGIN_GEOMETRY;
+ }
+
+ /**
+ * @return true if the display list is being built for painting.
+ */
+ bool IsForPainting()
+ {
+ return mMode == nsDisplayListBuilderMode::PAINTING;
+ }
+
+ /**
+ * @return true if the display list is being built for determining frame
+ * visibility.
+ */
+ bool IsForFrameVisibility()
+ {
+ return mMode == nsDisplayListBuilderMode::FRAME_VISIBILITY;
+ }
+
+ /**
+ * @return true if the display list is being built for creating the glyph
+ * mask from text items.
+ */
+ bool IsForGenerateGlyphMask()
+ {
+ return mMode == nsDisplayListBuilderMode::GENERATE_GLYPH;
+ }
+
+ /**
+ * @return true if the display list is being built for painting selection
+ * background.
+ */
+ bool IsForPaintingSelectionBG()
+ {
+ return mMode == nsDisplayListBuilderMode::PAINTING_SELECTION_BACKGROUND;
+ }
+
+ bool WillComputePluginGeometry() { return mWillComputePluginGeometry; }
+ /**
+ * @return true if "painting is suppressed" during page load and we
+ * should paint only the background of the document.
+ */
+ bool IsBackgroundOnly() {
+ NS_ASSERTION(mPresShellStates.Length() > 0,
+ "don't call this if we're not in a presshell");
+ return CurrentPresShellState()->mIsBackgroundOnly;
+ }
+ /**
+ * @return true if the currently active BuildDisplayList call is being
+ * applied to a frame at the root of a pseudo stacking context. A pseudo
+ * stacking context is either a real stacking context or basically what
+ * CSS2.1 appendix E refers to with "treat the element as if it created
+ * a new stacking context
+ */
+ bool IsAtRootOfPseudoStackingContext() { return mIsAtRootOfPseudoStackingContext; }
+
+ /**
+ * @return the selection that painting should be restricted to (or nullptr
+ * in the normal unrestricted case)
+ */
+ nsISelection* GetBoundingSelection() { return mBoundingSelection; }
+
+ /**
+ * @return the root of given frame's (sub)tree, whose origin
+ * establishes the coordinate system for the child display items.
+ */
+ const nsIFrame* FindReferenceFrameFor(const nsIFrame *aFrame,
+ nsPoint* aOffset = nullptr);
+
+ /**
+ * @return the root of the display list's frame (sub)tree, whose origin
+ * establishes the coordinate system for the display list
+ */
+ nsIFrame* RootReferenceFrame()
+ {
+ return mReferenceFrame;
+ }
+
+ /**
+ * @return a point pt such that adding pt to a coordinate relative to aFrame
+ * makes it relative to ReferenceFrame(), i.e., returns
+ * aFrame->GetOffsetToCrossDoc(ReferenceFrame()). The returned point is in
+ * the appunits of aFrame.
+ */
+ const nsPoint ToReferenceFrame(const nsIFrame* aFrame) {
+ nsPoint result;
+ FindReferenceFrameFor(aFrame, &result);
+ return result;
+ }
+ /**
+ * When building the display list, the scrollframe aFrame will be "ignored"
+ * for the purposes of clipping, and its scrollbars will be hidden. We use
+ * this to allow RenderOffscreen to render a whole document without beign
+ * clipped by the viewport or drawing the viewport scrollbars.
+ */
+ void SetIgnoreScrollFrame(nsIFrame* aFrame) { mIgnoreScrollFrame = aFrame; }
+ /**
+ * Get the scrollframe to ignore, if any.
+ */
+ nsIFrame* GetIgnoreScrollFrame() { return mIgnoreScrollFrame; }
+ /**
+ * Get the ViewID of the nearest scrolling ancestor frame.
+ */
+ ViewID GetCurrentScrollParentId() const { return mCurrentScrollParentId; }
+ /**
+ * Get and set the flag that indicates if scroll parents should have layers
+ * forcibly created. This flag is set when a deeply nested scrollframe has
+ * a displayport, and for scroll handoff to work properly the ancestor
+ * scrollframes should also get their own scrollable layers.
+ */
+ void ForceLayerForScrollParent() { mForceLayerForScrollParent = true; }
+ /**
+ * Get the ViewID and the scrollbar flags corresponding to the scrollbar for
+ * which we are building display items at the moment.
+ */
+ void GetScrollbarInfo(ViewID* aOutScrollbarTarget, uint32_t* aOutScrollbarFlags)
+ {
+ *aOutScrollbarTarget = mCurrentScrollbarTarget;
+ *aOutScrollbarFlags = mCurrentScrollbarFlags;
+ }
+ /**
+ * Returns true if building a scrollbar, and the scrollbar will not be
+ * layerized.
+ */
+ bool IsBuildingNonLayerizedScrollbar() const {
+ return mIsBuildingScrollbar && !mCurrentScrollbarWillHaveLayer;
+ }
+ /**
+ * Calling this setter makes us include all out-of-flow descendant
+ * frames in the display list, wherever they may be positioned (even
+ * outside the dirty rects).
+ */
+ void SetIncludeAllOutOfFlows() { mIncludeAllOutOfFlows = true; }
+ bool GetIncludeAllOutOfFlows() const { return mIncludeAllOutOfFlows; }
+ /**
+ * Calling this setter makes us exclude all leaf frames that aren't
+ * selected.
+ */
+ void SetSelectedFramesOnly() { mSelectedFramesOnly = true; }
+ bool GetSelectedFramesOnly() { return mSelectedFramesOnly; }
+ /**
+ * Calling this setter makes us compute accurate visible regions at the cost
+ * of performance if regions get very complex.
+ */
+ void SetAccurateVisibleRegions() { mAccurateVisibleRegions = true; }
+ bool GetAccurateVisibleRegions() { return mAccurateVisibleRegions; }
+ /**
+ * @return Returns true if we should include the caret in any display lists
+ * that we make.
+ */
+ bool IsBuildingCaret() { return mBuildCaret; }
+ /**
+ * Allows callers to selectively override the regular paint suppression checks,
+ * so that methods like GetFrameForPoint work when painting is suppressed.
+ */
+ void IgnorePaintSuppression() { mIgnoreSuppression = true; }
+ /**
+ * @return Returns if this builder will ignore paint suppression.
+ */
+ bool IsIgnoringPaintSuppression() { return mIgnoreSuppression; }
+ /**
+ * Call this if we're doing normal painting to the window.
+ */
+ void SetPaintingToWindow(bool aToWindow) { mIsPaintingToWindow = aToWindow; }
+ bool IsPaintingToWindow() const { return mIsPaintingToWindow; }
+ /**
+ * Call this to prevent descending into subdocuments.
+ */
+ void SetDescendIntoSubdocuments(bool aDescend) { mDescendIntoSubdocuments = aDescend; }
+ bool GetDescendIntoSubdocuments() { return mDescendIntoSubdocuments; }
+
+ /**
+ * Get dirty rect relative to current frame (the frame that we're calling
+ * BuildDisplayList on right now).
+ */
+ const nsRect& GetDirtyRect() { return mDirtyRect; }
+ const nsIFrame* GetCurrentFrame() { return mCurrentFrame; }
+ const nsIFrame* GetCurrentReferenceFrame() { return mCurrentReferenceFrame; }
+ const nsPoint& GetCurrentFrameOffsetToReferenceFrame() { return mCurrentOffsetToReferenceFrame; }
+ AnimatedGeometryRoot* GetCurrentAnimatedGeometryRoot() {
+ return mCurrentAGR;
+ }
+ AnimatedGeometryRoot* GetRootAnimatedGeometryRoot() {
+ return &mRootAGR;
+ }
+
+ void RecomputeCurrentAnimatedGeometryRoot();
+
+ /**
+ * Returns true if merging and flattening of display lists should be
+ * performed while computing visibility.
+ */
+ bool AllowMergingAndFlattening() { return mAllowMergingAndFlattening; }
+ void SetAllowMergingAndFlattening(bool aAllow) { mAllowMergingAndFlattening = aAllow; }
+
+ nsDisplayLayerEventRegions* GetLayerEventRegions() { return mLayerEventRegions; }
+ void SetLayerEventRegions(nsDisplayLayerEventRegions* aItem)
+ {
+ mLayerEventRegions = aItem;
+ }
+ bool IsBuildingLayerEventRegions();
+ static bool LayerEventRegionsEnabled();
+ bool IsInsidePointerEventsNoneDoc()
+ {
+ return CurrentPresShellState()->mInsidePointerEventsNoneDoc;
+ }
+
+ bool GetAncestorHasApzAwareEventHandler() { return mAncestorHasApzAwareEventHandler; }
+ void SetAncestorHasApzAwareEventHandler(bool aValue)
+ {
+ mAncestorHasApzAwareEventHandler = aValue;
+ }
+
+ bool HaveScrollableDisplayPort() const { return mHaveScrollableDisplayPort; }
+ void SetHaveScrollableDisplayPort() { mHaveScrollableDisplayPort = true; }
+
+ bool SetIsCompositingCheap(bool aCompositingCheap) {
+ bool temp = mIsCompositingCheap;
+ mIsCompositingCheap = aCompositingCheap;
+ return temp;
+ }
+ bool IsCompositingCheap() const { return mIsCompositingCheap; }
+ /**
+ * Display the caret if needed.
+ */
+ void DisplayCaret(nsIFrame* aFrame, const nsRect& aDirtyRect,
+ nsDisplayList* aList) {
+ nsIFrame* frame = GetCaretFrame();
+ if (aFrame == frame) {
+ frame->DisplayCaret(this, aDirtyRect, aList);
+ }
+ }
+ /**
+ * Get the frame that the caret is supposed to draw in.
+ * If the caret is currently invisible, this will be null.
+ */
+ nsIFrame* GetCaretFrame() {
+ return CurrentPresShellState()->mCaretFrame;
+ }
+ /**
+ * Get the rectangle we're supposed to draw the caret into.
+ */
+ const nsRect& GetCaretRect() {
+ return CurrentPresShellState()->mCaretRect;
+ }
+ /**
+ * Get the caret associated with the current presshell.
+ */
+ nsCaret* GetCaret();
+ /**
+ * Notify the display list builder that we're entering a presshell.
+ * aReferenceFrame should be a frame in the new presshell.
+ * aPointerEventsNoneDoc should be set to true if the frame generating this
+ * document is pointer-events:none.
+ */
+ void EnterPresShell(nsIFrame* aReferenceFrame,
+ bool aPointerEventsNoneDoc = false);
+ /**
+ * For print-preview documents, we sometimes need to build display items for
+ * the same frames multiple times in the same presentation, with different
+ * clipping. Between each such batch of items, call
+ * ResetMarkedFramesForDisplayList to make sure that the results of
+ * MarkFramesForDisplayList do not carry over between batches.
+ */
+ void ResetMarkedFramesForDisplayList();
+ /**
+ * Notify the display list builder that we're leaving a presshell.
+ */
+ void LeavePresShell(nsIFrame* aReferenceFrame, nsDisplayList* aPaintedContents);
+
+ /**
+ * Returns true if we're currently building a display list that's
+ * directly or indirectly under an nsDisplayTransform.
+ */
+ bool IsInTransform() const { return mInTransform; }
+ /**
+ * Indicate whether or not we're directly or indirectly under and
+ * nsDisplayTransform or SVG foreignObject.
+ */
+ void SetInTransform(bool aInTransform) { mInTransform = aInTransform; }
+
+ /**
+ * Return true if we're currently building a display list for a
+ * nested presshell.
+ */
+ bool IsInSubdocument() { return mPresShellStates.Length() > 1; }
+
+ /**
+ * Return true if we're currently building a display list for the presshell
+ * of a chrome document, or if we're building the display list for a popup.
+ */
+ bool IsInChromeDocumentOrPopup() {
+ return mIsInChromePresContext || mIsBuildingForPopup;
+ }
+
+ /**
+ * @return true if images have been set to decode synchronously.
+ */
+ bool ShouldSyncDecodeImages() { return mSyncDecodeImages; }
+
+ /**
+ * Indicates whether we should synchronously decode images. If true, we decode
+ * and draw whatever image data has been loaded. If false, we just draw
+ * whatever has already been decoded.
+ */
+ void SetSyncDecodeImages(bool aSyncDecodeImages) {
+ mSyncDecodeImages = aSyncDecodeImages;
+ }
+
+ /**
+ * Helper method to generate background painting flags based on the
+ * information available in the display list builder. Currently only
+ * accounts for mSyncDecodeImages.
+ */
+ uint32_t GetBackgroundPaintFlags();
+
+ /**
+ * Subtracts aRegion from *aVisibleRegion. We avoid letting
+ * aVisibleRegion become overcomplex by simplifying it if necessary ---
+ * unless mAccurateVisibleRegions is set, in which case we let it
+ * get arbitrarily complex.
+ */
+ void SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
+ const nsRegion& aRegion);
+
+ /**
+ * Mark the frames in aFrames to be displayed if they intersect aDirtyRect
+ * (which is relative to aDirtyFrame). If the frames have placeholders
+ * that might not be displayed, we mark the placeholders and their ancestors
+ * to ensure that display list construction descends into them
+ * anyway. nsDisplayListBuilder will take care of unmarking them when it is
+ * destroyed.
+ */
+ void MarkFramesForDisplayList(nsIFrame* aDirtyFrame,
+ const nsFrameList& aFrames,
+ const nsRect& aDirtyRect);
+ /**
+ * Mark all child frames that Preserve3D() as needing display.
+ * Because these frames include transforms set on their parent, dirty rects
+ * for intermediate frames may be empty, yet child frames could still be visible.
+ */
+ void MarkPreserve3DFramesForDisplayList(nsIFrame* aDirtyFrame);
+
+ const nsTArray<ThemeGeometry>& GetThemeGeometries() { return mThemeGeometries; }
+
+ /**
+ * Returns true if we need to descend into this frame when building
+ * the display list, even though it doesn't intersect the dirty
+ * rect, because it may have out-of-flows that do so.
+ */
+ bool ShouldDescendIntoFrame(nsIFrame* aFrame) const {
+ return
+ (aFrame->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
+ GetIncludeAllOutOfFlows();
+ }
+
+ /**
+ * Notifies the builder that a particular themed widget exists
+ * at the given rectangle within the currently built display list.
+ * For certain appearance values (currently only NS_THEME_TOOLBAR and
+ * NS_THEME_WINDOW_TITLEBAR) this gets called during every display list
+ * construction, for every themed widget of the right type within the
+ * display list, except for themed widgets which are transformed or have
+ * effects applied to them (e.g. CSS opacity or filters).
+ *
+ * @param aWidgetType the -moz-appearance value for the themed widget
+ * @param aRect the device-pixel rect relative to the widget's displayRoot
+ * for the themed widget
+ */
+ void RegisterThemeGeometry(uint8_t aWidgetType,
+ const mozilla::LayoutDeviceIntRect& aRect) {
+ if (mIsPaintingToWindow) {
+ mThemeGeometries.AppendElement(ThemeGeometry(aWidgetType, aRect));
+ }
+ }
+
+ /**
+ * Adjusts mWindowDraggingRegion to take into account aFrame. If aFrame's
+ * -moz-window-dragging value is |drag|, its border box is added to the
+ * collected dragging region; if the value is |no-drag|, the border box is
+ * subtracted from the region; if the value is |default|, that frame does
+ * not influence the window dragging region.
+ */
+ void AdjustWindowDraggingRegion(nsIFrame* aFrame);
+
+ LayoutDeviceIntRegion GetWindowDraggingRegion() const;
+
+ /**
+ * Allocate memory in our arena. It will only be freed when this display list
+ * builder is destroyed. This memory holds nsDisplayItems. nsDisplayItem
+ * destructors are called as soon as the item is no longer used.
+ */
+ void* Allocate(size_t aSize);
+
+ /**
+ * Allocate a new DisplayItemClip in the arena. Will be cleaned up
+ * automatically when the arena goes away.
+ */
+ const DisplayItemClip* AllocateDisplayItemClip(const DisplayItemClip& aOriginal);
+
+ /**
+ * Allocate a new DisplayItemScrollClip in the arena. Will be cleaned up
+ * automatically when the arena goes away.
+ */
+ DisplayItemScrollClip* AllocateDisplayItemScrollClip(const DisplayItemScrollClip* aParent,
+ nsIScrollableFrame* aScrollableFrame,
+ const DisplayItemClip* aClip,
+ bool aIsAsyncScrollable);
+
+ /**
+ * Transfer off main thread animations to the layer. May be called
+ * with aBuilder and aItem both null, but only if the caller has
+ * already checked that off main thread animations should be sent to
+ * the layer. When they are both null, the animations are added to
+ * the layer as pending animations.
+ */
+ static void AddAnimationsAndTransitionsToLayer(Layer* aLayer,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+
+ /**
+ * A helper class to temporarily set the value of
+ * mIsAtRootOfPseudoStackingContext, and temporarily
+ * set mCurrentFrame and related state. Also temporarily sets mDirtyRect.
+ * aDirtyRect is relative to aForChild.
+ */
+ class AutoBuildingDisplayList;
+ friend class AutoBuildingDisplayList;
+ class AutoBuildingDisplayList {
+ public:
+ AutoBuildingDisplayList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aForChild,
+ const nsRect& aDirtyRect, bool aIsRoot)
+ : mBuilder(aBuilder),
+ mPrevFrame(aBuilder->mCurrentFrame),
+ mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame),
+ mPrevLayerEventRegions(aBuilder->mLayerEventRegions),
+ mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame),
+ mPrevDirtyRect(aBuilder->mDirtyRect),
+ mPrevAGR(aBuilder->mCurrentAGR),
+ mPrevIsAtRootOfPseudoStackingContext(aBuilder->mIsAtRootOfPseudoStackingContext),
+ mPrevAncestorHasApzAwareEventHandler(aBuilder->mAncestorHasApzAwareEventHandler),
+ mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems)
+ {
+ if (aForChild->IsTransformed()) {
+ aBuilder->mCurrentOffsetToReferenceFrame = nsPoint();
+ aBuilder->mCurrentReferenceFrame = aForChild;
+ } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
+ aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition();
+ } else {
+ aBuilder->mCurrentReferenceFrame =
+ aBuilder->FindReferenceFrameFor(aForChild,
+ &aBuilder->mCurrentOffsetToReferenceFrame);
+ }
+ if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
+ if (aBuilder->IsAnimatedGeometryRoot(aForChild)) {
+ aBuilder->mCurrentAGR = aBuilder->WrapAGRForFrame(aForChild, aBuilder->mCurrentAGR);
+ }
+ } else if (aForChild != aBuilder->mCurrentFrame) {
+ aBuilder->mCurrentAGR = aBuilder->FindAnimatedGeometryRootFor(aForChild);
+ }
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(), *aBuilder->mCurrentAGR));
+ aBuilder->mCurrentFrame = aForChild;
+ aBuilder->mDirtyRect = aDirtyRect;
+ aBuilder->mIsAtRootOfPseudoStackingContext = aIsRoot;
+ }
+ void SetDirtyRect(const nsRect& aRect) {
+ mBuilder->mDirtyRect = aRect;
+ }
+ void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, const nsPoint& aOffset) {
+ mBuilder->mCurrentReferenceFrame = aFrame;
+ mBuilder->mCurrentOffsetToReferenceFrame = aOffset;
+ }
+ // Return the previous frame's animated geometry root, whether or not the
+ // current frame is an immediate descendant.
+ const nsIFrame* GetPrevAnimatedGeometryRoot() const {
+ return mPrevAnimatedGeometryRoot;
+ }
+ bool IsAnimatedGeometryRoot() const {
+ return *mBuilder->mCurrentAGR == mBuilder->mCurrentFrame;
+
+ }
+ void RestoreBuildingInvisibleItemsValue() {
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ }
+ ~AutoBuildingDisplayList() {
+ mBuilder->mCurrentFrame = mPrevFrame;
+ mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame;
+ mBuilder->mLayerEventRegions = mPrevLayerEventRegions;
+ mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset;
+ mBuilder->mDirtyRect = mPrevDirtyRect;
+ mBuilder->mCurrentAGR = mPrevAGR;
+ mBuilder->mIsAtRootOfPseudoStackingContext = mPrevIsAtRootOfPseudoStackingContext;
+ mBuilder->mAncestorHasApzAwareEventHandler = mPrevAncestorHasApzAwareEventHandler;
+ mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+ }
+ private:
+ nsDisplayListBuilder* mBuilder;
+ const nsIFrame* mPrevFrame;
+ const nsIFrame* mPrevReferenceFrame;
+ nsIFrame* mPrevAnimatedGeometryRoot;
+ nsDisplayLayerEventRegions* mPrevLayerEventRegions;
+ nsPoint mPrevOffset;
+ nsRect mPrevDirtyRect;
+ AnimatedGeometryRoot* mPrevAGR;
+ bool mPrevIsAtRootOfPseudoStackingContext;
+ bool mPrevAncestorHasApzAwareEventHandler;
+ bool mPrevBuildingInvisibleItems;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mInTransform.
+ */
+ class AutoInTransformSetter;
+ friend class AutoInTransformSetter;
+ class AutoInTransformSetter {
+ public:
+ AutoInTransformSetter(nsDisplayListBuilder* aBuilder, bool aInTransform)
+ : mBuilder(aBuilder), mOldValue(aBuilder->mInTransform) {
+ aBuilder->mInTransform = aInTransform;
+ }
+ ~AutoInTransformSetter() {
+ mBuilder->mInTransform = mOldValue;
+ }
+ private:
+ nsDisplayListBuilder* mBuilder;
+ bool mOldValue;
+ };
+
+ class AutoSaveRestorePerspectiveIndex;
+ friend class AutoSaveRestorePerspectiveIndex;
+ class AutoSaveRestorePerspectiveIndex {
+ public:
+ AutoSaveRestorePerspectiveIndex(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : mBuilder(nullptr)
+ {
+ if (aFrame->ChildrenHavePerspective()) {
+ mBuilder = aBuilder;
+ mCachedItemIndex = aBuilder->mPerspectiveItemIndex;
+ aBuilder->mPerspectiveItemIndex = 0;
+ }
+ }
+
+ ~AutoSaveRestorePerspectiveIndex()
+ {
+ if (mBuilder) {
+ mBuilder->mPerspectiveItemIndex = mCachedItemIndex;
+ }
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ uint32_t mCachedItemIndex;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mCurrentScrollParentId.
+ */
+ class AutoCurrentScrollParentIdSetter;
+ friend class AutoCurrentScrollParentIdSetter;
+ class AutoCurrentScrollParentIdSetter {
+ public:
+ AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder, ViewID aScrollId)
+ : mBuilder(aBuilder)
+ , mOldValue(aBuilder->mCurrentScrollParentId)
+ , mOldForceLayer(aBuilder->mForceLayerForScrollParent) {
+ // If this AutoCurrentScrollParentIdSetter has the same scrollId as the
+ // previous one on the stack, then that means the scrollframe that
+ // created this isn't actually scrollable and cannot participate in
+ // scroll handoff. We set mCanBeScrollParent to false to indicate this.
+ mCanBeScrollParent = (mOldValue != aScrollId);
+ aBuilder->mCurrentScrollParentId = aScrollId;
+ aBuilder->mForceLayerForScrollParent = false;
+ }
+ bool ShouldForceLayerForScrollParent() const {
+ // Only scrollframes participating in scroll handoff can be forced to
+ // layerize
+ return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent;
+ };
+ ~AutoCurrentScrollParentIdSetter() {
+ mBuilder->mCurrentScrollParentId = mOldValue;
+ if (mCanBeScrollParent) {
+ // If this flag is set, caller code is responsible for having dealt
+ // with the current value of mBuilder->mForceLayerForScrollParent, so
+ // we can just restore the old value.
+ mBuilder->mForceLayerForScrollParent = mOldForceLayer;
+ } else {
+ // Otherwise we need to keep propagating the force-layerization flag
+ // upwards to the next ancestor scrollframe that does participate in
+ // scroll handoff.
+ mBuilder->mForceLayerForScrollParent |= mOldForceLayer;
+ }
+ }
+ private:
+ nsDisplayListBuilder* mBuilder;
+ ViewID mOldValue;
+ bool mOldForceLayer;
+ bool mCanBeScrollParent;
+ };
+
+ /**
+ * A helper class to temporarily set the value of mCurrentScrollbarTarget
+ * and mCurrentScrollbarFlags.
+ */
+ class AutoCurrentScrollbarInfoSetter;
+ friend class AutoCurrentScrollbarInfoSetter;
+ class AutoCurrentScrollbarInfoSetter {
+ public:
+ AutoCurrentScrollbarInfoSetter(nsDisplayListBuilder* aBuilder, ViewID aScrollTargetID,
+ uint32_t aScrollbarFlags, bool aWillHaveLayer)
+ : mBuilder(aBuilder) {
+ aBuilder->mIsBuildingScrollbar = true;
+ aBuilder->mCurrentScrollbarTarget = aScrollTargetID;
+ aBuilder->mCurrentScrollbarFlags = aScrollbarFlags;
+ aBuilder->mCurrentScrollbarWillHaveLayer = aWillHaveLayer;
+ }
+ ~AutoCurrentScrollbarInfoSetter() {
+ // No need to restore old values because scrollbars cannot be nested.
+ mBuilder->mIsBuildingScrollbar = false;
+ mBuilder->mCurrentScrollbarTarget = FrameMetrics::NULL_SCROLL_ID;
+ mBuilder->mCurrentScrollbarFlags = 0;
+ mBuilder->mCurrentScrollbarWillHaveLayer = false;
+ }
+ private:
+ nsDisplayListBuilder* mBuilder;
+ };
+
+ /**
+ * A helper class to track current effective transform for items.
+ *
+ * For frames that is Combines3DTransformWithAncestors(), we need to
+ * apply all transforms of ancestors on the same preserves3D chain
+ * on the bounds of current frame to the coordination of the 3D
+ * context root. The 3D context root computes it's bounds from
+ * these transformed bounds.
+ */
+ class AutoAccumulateTransform;
+ friend class AutoAccumulateTransform;
+ class AutoAccumulateTransform {
+ public:
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+ explicit AutoAccumulateTransform(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder)
+ , mSavedTransform(aBuilder->mPreserves3DCtx.mAccumulatedTransform) {}
+
+ ~AutoAccumulateTransform() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = mSavedTransform;
+ }
+
+ void Accumulate(const Matrix4x4& aTransform) {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform =
+ aTransform * mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ const Matrix4x4& GetCurrentTransform() {
+ return mBuilder->mPreserves3DCtx.mAccumulatedTransform;
+ }
+
+ void StartRoot() {
+ mBuilder->mPreserves3DCtx.mAccumulatedTransform = Matrix4x4();
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ Matrix4x4 mSavedTransform;
+ };
+
+ /**
+ * A helper class to collect bounds rects of descendants.
+ *
+ * For a 3D context root, it's bounds is computed from the bounds of
+ * descendants. If we transform bounds frame by frame applying
+ * transforms, the bounds may turn to empty for any singular
+ * transform on the path, but it is not empty for the accumulated
+ * transform.
+ */
+ class AutoAccumulateRect;
+ friend class AutoAccumulateRect;
+ class AutoAccumulateRect {
+ public:
+ explicit AutoAccumulateRect(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder)
+ , mSavedRect(aBuilder->mPreserves3DCtx.mAccumulatedRect) {
+ aBuilder->mPreserves3DCtx.mAccumulatedRect = nsRect();
+ aBuilder->mPreserves3DCtx.mAccumulatedRectLevels++;
+ }
+ ~AutoAccumulateRect() {
+ mBuilder->mPreserves3DCtx.mAccumulatedRect = mSavedRect;
+ mBuilder->mPreserves3DCtx.mAccumulatedRectLevels--;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ nsRect mSavedRect;
+ };
+
+ void AccumulateRect(const nsRect& aRect) {
+ mPreserves3DCtx.mAccumulatedRect.UnionRect(mPreserves3DCtx.mAccumulatedRect, aRect);
+ }
+ const nsRect& GetAccumulatedRect() {
+ return mPreserves3DCtx.mAccumulatedRect;
+ }
+ /**
+ * The level is increased by one for items establishing 3D rendering
+ * context and starting a new accumulation.
+ */
+ int GetAccumulatedRectLevels() {
+ return mPreserves3DCtx.mAccumulatedRectLevels;
+ }
+
+ // Helpers for tables
+ nsDisplayTableItem* GetCurrentTableItem() { return mCurrentTableItem; }
+ void SetCurrentTableItem(nsDisplayTableItem* aTableItem) { mCurrentTableItem = aTableItem; }
+
+ struct OutOfFlowDisplayData {
+ OutOfFlowDisplayData(const DisplayItemClip* aContainingBlockClip,
+ const DisplayItemScrollClip* aContainingBlockScrollClip,
+ const nsRect &aDirtyRect)
+ : mContainingBlockClip(aContainingBlockClip ? *aContainingBlockClip : DisplayItemClip())
+ , mContainingBlockScrollClip(aContainingBlockScrollClip)
+ , mDirtyRect(aDirtyRect)
+ {}
+ DisplayItemClip mContainingBlockClip;
+ const DisplayItemScrollClip* mContainingBlockScrollClip;
+ nsRect mDirtyRect;
+ };
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutOfFlowDisplayDataProperty,
+ OutOfFlowDisplayData)
+
+ static OutOfFlowDisplayData* GetOutOfFlowData(nsIFrame* aFrame)
+ {
+ return aFrame->Properties().Get(OutOfFlowDisplayDataProperty());
+ }
+
+ nsPresContext* CurrentPresContext() {
+ return CurrentPresShellState()->mPresShell->GetPresContext();
+ }
+
+ /**
+ * Accumulates the bounds of box frames that have moz-appearance
+ * -moz-win-exclude-glass style. Used in setting glass margins on
+ * Windows.
+ *
+ * We set the window opaque region (from which glass margins are computed)
+ * to the intersection of the glass region specified here and the opaque
+ * region computed during painting. So the excluded glass region actually
+ * *limits* the extent of the opaque area reported to Windows. We limit it
+ * so that changes to the computed opaque region (which can vary based on
+ * region optimizations and the placement of UI elements) outside the
+ * -moz-win-exclude-glass area don't affect the glass margins reported to
+ * Windows; changing those margins willy-nilly can cause the Windows 7 glass
+ * haze effect to jump around disconcertingly.
+ */
+ void AddWindowExcludeGlassRegion(const nsRegion& bounds) {
+ mWindowExcludeGlassRegion.Or(mWindowExcludeGlassRegion, bounds);
+ }
+ const nsRegion& GetWindowExcludeGlassRegion() {
+ return mWindowExcludeGlassRegion;
+ }
+ /**
+ * Accumulates opaque stuff into the window opaque region.
+ */
+ void AddWindowOpaqueRegion(const nsRegion& bounds) {
+ mWindowOpaqueRegion.Or(mWindowOpaqueRegion, bounds);
+ }
+ /**
+ * Returns the window opaque region built so far. This may be incomplete
+ * since the opaque region is built during layer construction.
+ */
+ const nsRegion& GetWindowOpaqueRegion() {
+ return mWindowOpaqueRegion;
+ }
+ void SetGlassDisplayItem(nsDisplayItem* aItem) {
+ if (mGlassDisplayItem) {
+ // Web pages or extensions could trigger this by using
+ // -moz-appearance:win-borderless-glass etc on their own elements.
+ // Keep the first one, since that will be the background of the root
+ // window
+ NS_WARNING("Multiple glass backgrounds found?");
+ } else {
+ mGlassDisplayItem = aItem;
+ }
+ }
+ bool NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem);
+
+ void SetContainsPluginItem() { mContainsPluginItem = true; }
+ bool ContainsPluginItem() { return mContainsPluginItem; }
+
+ /**
+ * mContainsBlendMode is true if we processed a display item that
+ * has a blend mode attached. We do this so we can insert a
+ * nsDisplayBlendContainer in the parent stacking context.
+ */
+ void SetContainsBlendMode(bool aContainsBlendMode) { mContainsBlendMode = aContainsBlendMode; }
+ bool ContainsBlendMode() const { return mContainsBlendMode; }
+
+ uint32_t AllocatePerspectiveItemIndex() { return mPerspectiveItemIndex++; }
+
+ DisplayListClipState& ClipState() { return mClipState; }
+
+ /**
+ * Add the current frame to the will-change budget if possible and
+ * remeber the outcome. Subsequent calls to IsInWillChangeBudget
+ * will return the same value as return here.
+ */
+ bool AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
+
+ /**
+ * This will add the current frame to the will-change budget the first
+ * time it is seen. On subsequent calls this will return the same
+ * answer. This effectively implements a first-come, first-served
+ * allocation of the will-change budget.
+ */
+ bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
+
+ void EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage);
+ void ExitSVGEffectsContents();
+
+ bool ShouldBuildScrollInfoItemsForHoisting() const
+ { return mSVGEffectsBuildingDepth > 0; }
+
+ void AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLayer* aScrollInfoItem);
+
+ /**
+ * A helper class to install/restore nsDisplayListBuilder::mPreserves3DCtx.
+ *
+ * mPreserves3DCtx is used by class AutoAccumulateTransform &
+ * AutoAccumulateRect to passing data between frames in the 3D
+ * context. If a frame create a new 3D context, it should restore
+ * the value of mPreserves3DCtx before returning back to the parent.
+ * This class do it for the users.
+ */
+ class AutoPreserves3DContext;
+ friend class AutoPreserves3DContext;
+ class AutoPreserves3DContext {
+ public:
+ explicit AutoPreserves3DContext(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder)
+ , mSavedCtx(aBuilder->mPreserves3DCtx) {}
+ ~AutoPreserves3DContext() {
+ mBuilder->mPreserves3DCtx = mSavedCtx;
+ }
+
+ private:
+ nsDisplayListBuilder* mBuilder;
+ Preserves3DContext mSavedCtx;
+ };
+
+ const nsRect GetPreserves3DDirtyRect(const nsIFrame *aFrame) const {
+ return mPreserves3DCtx.mDirtyRect;
+ }
+ void SetPreserves3DDirtyRect(const nsRect &aDirtyRect) {
+ mPreserves3DCtx.mDirtyRect = aDirtyRect;
+ }
+
+ bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; }
+ void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) {
+ mBuildingInvisibleItems = aBuildingInvisibleItems;
+ }
+
+private:
+ void MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame,
+ const nsRect& aDirtyRect);
+
+ /**
+ * Returns whether a frame acts as an animated geometry root, optionally
+ * returning the next ancestor to check.
+ */
+ bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr);
+
+ /**
+ * Returns the nearest ancestor frame to aFrame that is considered to have
+ * (or will have) animated geometry. This can return aFrame.
+ */
+ nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame);
+
+ friend class nsDisplayCanvasBackgroundImage;
+ friend class nsDisplayBackgroundImage;
+ friend class nsDisplayFixedPosition;
+ AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsDisplayItem* aItem);
+
+ AnimatedGeometryRoot* WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
+ AnimatedGeometryRoot* aParent = nullptr);
+
+ friend class nsDisplayItem;
+ AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsIFrame* aFrame);
+
+ nsDataHashtable<nsPtrHashKey<nsIFrame>, AnimatedGeometryRoot*> mFrameToAnimatedGeometryRootMap;
+
+ /**
+ * Add the current frame to the AGR budget if possible and remember
+ * the outcome. Subsequent calls will return the same value as
+ * returned here.
+ */
+ bool AddToAGRBudget(nsIFrame* aFrame);
+
+ struct PresShellState {
+ nsIPresShell* mPresShell;
+ nsIFrame* mCaretFrame;
+ nsRect mCaretRect;
+ uint32_t mFirstFrameMarkedForDisplay;
+ bool mIsBackgroundOnly;
+ // This is a per-document flag turning off event handling for all content
+ // in the document, and is set when we enter a subdocument for a pointer-
+ // events:none frame.
+ bool mInsidePointerEventsNoneDoc;
+ };
+
+ PresShellState* CurrentPresShellState() {
+ NS_ASSERTION(mPresShellStates.Length() > 0,
+ "Someone forgot to enter a presshell");
+ return &mPresShellStates[mPresShellStates.Length() - 1];
+ }
+
+ struct DocumentWillChangeBudget {
+ DocumentWillChangeBudget()
+ : mBudget(0)
+ {}
+
+ uint32_t mBudget;
+ };
+
+ nsIFrame* const mReferenceFrame;
+ nsIFrame* mIgnoreScrollFrame;
+ nsDisplayLayerEventRegions* mLayerEventRegions;
+ PLArenaPool mPool;
+ nsCOMPtr<nsISelection> mBoundingSelection;
+ AutoTArray<PresShellState,8> mPresShellStates;
+ AutoTArray<nsIFrame*,100> mFramesMarkedForDisplay;
+ AutoTArray<ThemeGeometry,2> mThemeGeometries;
+ nsDisplayTableItem* mCurrentTableItem;
+ DisplayListClipState mClipState;
+ // mCurrentFrame is the frame that we're currently calling (or about to call)
+ // BuildDisplayList on.
+ const nsIFrame* mCurrentFrame;
+ // The reference frame for mCurrentFrame.
+ const nsIFrame* mCurrentReferenceFrame;
+ // The offset from mCurrentFrame to mCurrentReferenceFrame.
+ nsPoint mCurrentOffsetToReferenceFrame;
+
+ AnimatedGeometryRoot* mCurrentAGR;
+ AnimatedGeometryRoot mRootAGR;
+
+ // will-change budget tracker
+ nsDataHashtable<nsPtrHashKey<nsPresContext>, DocumentWillChangeBudget>
+ mWillChangeBudget;
+
+ // Any frame listed in this set is already counted in the budget
+ // and thus is in-budget.
+ nsTHashtable<nsPtrHashKey<nsIFrame> > mWillChangeBudgetSet;
+
+ // Area of animated geometry root budget already allocated
+ uint32_t mUsedAGRBudget;
+ // Set of frames already counted in budget
+ nsTHashtable<nsPtrHashKey<nsIFrame> > mAGRBudgetSet;
+
+ // Relative to mCurrentFrame.
+ nsRect mDirtyRect;
+ nsRegion mWindowExcludeGlassRegion;
+ nsRegion mWindowOpaqueRegion;
+ LayoutDeviceIntRegion mWindowDraggingRegion;
+ LayoutDeviceIntRegion mWindowNoDraggingRegion;
+ // The display item for the Windows window glass background, if any
+ nsDisplayItem* mGlassDisplayItem;
+ // A temporary list that we append scroll info items to while building
+ // display items for the contents of frames with SVG effects.
+ // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true.
+ // This is a pointer and not a real nsDisplayList value because the
+ // nsDisplayList class is defined below this class, so we can't use it here.
+ nsDisplayList* mScrollInfoItemsForHoisting;
+ nsTArray<DisplayItemScrollClip*> mScrollClipsToDestroy;
+ nsTArray<DisplayItemClip*> mDisplayItemClipsToDestroy;
+ nsDisplayListBuilderMode mMode;
+ ViewID mCurrentScrollParentId;
+ ViewID mCurrentScrollbarTarget;
+ uint32_t mCurrentScrollbarFlags;
+ Preserves3DContext mPreserves3DCtx;
+ uint32_t mPerspectiveItemIndex;
+ int32_t mSVGEffectsBuildingDepth;
+ bool mContainsBlendMode;
+ bool mIsBuildingScrollbar;
+ bool mCurrentScrollbarWillHaveLayer;
+ bool mBuildCaret;
+ bool mIgnoreSuppression;
+ bool mIsAtRootOfPseudoStackingContext;
+ bool mIncludeAllOutOfFlows;
+ bool mDescendIntoSubdocuments;
+ bool mSelectedFramesOnly;
+ bool mAccurateVisibleRegions;
+ bool mAllowMergingAndFlattening;
+ bool mWillComputePluginGeometry;
+ // True when we're building a display list that's directly or indirectly
+ // under an nsDisplayTransform
+ bool mInTransform;
+ bool mIsInChromePresContext;
+ bool mSyncDecodeImages;
+ bool mIsPaintingToWindow;
+ bool mIsCompositingCheap;
+ bool mContainsPluginItem;
+ bool mAncestorHasApzAwareEventHandler;
+ // True when the first async-scrollable scroll frame for which we build a
+ // display list has a display port. An async-scrollable scroll frame is one
+ // which WantsAsyncScroll().
+ bool mHaveScrollableDisplayPort;
+ bool mWindowDraggingAllowed;
+ bool mIsBuildingForPopup;
+ bool mForceLayerForScrollParent;
+ bool mAsyncPanZoomEnabled;
+ bool mBuildingInvisibleItems;
+};
+
+class nsDisplayItem;
+class nsDisplayList;
+/**
+ * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList.
+ * nsDisplayItemLink holds the link. The lists are linked from lowest to
+ * highest in z-order.
+ */
+class nsDisplayItemLink {
+ // This is never instantiated directly, so no need to count constructors and
+ // destructors.
+protected:
+ nsDisplayItemLink() : mAbove(nullptr) {}
+ nsDisplayItem* mAbove;
+
+ friend class nsDisplayList;
+};
+
+/**
+ * This is the unit of rendering and event testing. Each instance of this
+ * class represents an entity that can be drawn on the screen, e.g., a
+ * frame's CSS background, or a frame's text string.
+ *
+ * nsDisplayItems can be containers --- i.e., they can perform hit testing
+ * and painting by recursively traversing a list of child items.
+ *
+ * These are arena-allocated during display list construction. A typical
+ * subclass would just have a frame pointer, so its object would be just three
+ * pointers (vtable, next-item, frame).
+ *
+ * Display items belong to a list at all times (except temporarily as they
+ * move from one list to another).
+ */
+class nsDisplayItem : public nsDisplayItemLink {
+public:
+ typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
+ typedef mozilla::DisplayItemClip DisplayItemClip;
+ typedef mozilla::DisplayItemScrollClip DisplayItemScrollClip;
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollMetadata ScrollMetadata;
+ typedef mozilla::layers::FrameMetrics::ViewID ViewID;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::LayerState LayerState;
+
+ // This is never instantiated directly (it has pure virtual methods), so no
+ // need to count constructors and destructors.
+ nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+ nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const DisplayItemScrollClip* aScrollClip);
+ /**
+ * This constructor is only used in rare cases when we need to construct
+ * temporary items.
+ */
+ explicit nsDisplayItem(nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mClip(nullptr)
+ , mScrollClip(nullptr)
+ , mReferenceFrame(nullptr)
+ , mAnimatedGeometryRoot(nullptr)
+ , mForceNotVisible(false)
+#ifdef MOZ_DUMP_PAINTING
+ , mPainted(false)
+#endif
+ {
+ }
+ virtual ~nsDisplayItem() {}
+
+ void* operator new(size_t aSize,
+ nsDisplayListBuilder* aBuilder) {
+ return aBuilder->Allocate(aSize);
+ }
+
+// Contains all the type integers for each display list item type
+#include "nsDisplayItemTypes.h"
+
+ struct HitTestState {
+ explicit HitTestState() : mInPreserves3D(false) {}
+
+ ~HitTestState() {
+ NS_ASSERTION(mItemBuffer.Length() == 0,
+ "mItemBuffer should have been cleared");
+ }
+
+ // Handling transform items for preserve 3D frames.
+ bool mInPreserves3D;
+ AutoTArray<nsDisplayItem*, 100> mItemBuffer;
+ };
+
+ /**
+ * Some consecutive items should be rendered together as a unit, e.g.,
+ * outlines for the same element. For this, we need a way for items to
+ * identify their type. We use the type for other purposes too.
+ */
+ virtual Type GetType() = 0;
+ /**
+ * Pairing this with the GetUnderlyingFrame() pointer gives a key that
+ * uniquely identifies this display item in the display item tree.
+ * XXX check nsOptionEventGrabberWrapper/nsXULEventRedirectorWrapper
+ */
+ virtual uint32_t GetPerFrameKey() { return uint32_t(GetType()); }
+ /**
+ * This is called after we've constructed a display list for event handling.
+ * When this is called, we've already ensured that aRect intersects the
+ * item's bounds and that clipping has been taking into account.
+ *
+ * @param aRect the point or rect being tested, relative to the reference
+ * frame. If the width and height are both 1 app unit, it indicates we're
+ * hit testing a point, not a rect.
+ * @param aState must point to a HitTestState. If you don't have one,
+ * just create one with the default constructor and pass it in.
+ * @param aOutFrames each item appends the frame(s) in this display item that
+ * the rect is considered over (if any) to aOutFrames.
+ */
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {}
+ /**
+ * @return the frame that this display item is based on. This is used to sort
+ * items by z-index and content order and for some other uses. Never
+ * returns null.
+ */
+ inline nsIFrame* Frame() const { return mFrame; }
+ /**
+ * Compute the used z-index of our frame; returns zero for elements to which
+ * z-index does not apply, and for z-index:auto.
+ * @note This can be overridden, @see nsDisplayWrapList::SetOverrideZIndex.
+ */
+ virtual int32_t ZIndex() const;
+ /**
+ * The default bounds is the frame border rect.
+ * @param aSnap *aSnap is set to true if the returned rect will be
+ * snapped to nearest device pixel edges during actual drawing.
+ * It might be set to false and snap anyway, so code computing the set of
+ * pixels affected by this display item needs to round outwards to pixel
+ * boundaries when *aSnap is set to false.
+ * This does not take the item's clipping into account.
+ * @return a rectangle relative to aBuilder->ReferenceFrame() that
+ * contains the area drawn by this display item
+ */
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+ {
+ *aSnap = false;
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+ /**
+ * Returns true if nothing will be rendered inside aRect, false if uncertain.
+ * aRect is assumed to be contained in this item's bounds.
+ */
+ virtual bool IsInvisibleInRect(const nsRect& aRect)
+ {
+ return false;
+ }
+ /**
+ * Returns the result of GetBounds intersected with the item's clip.
+ * The intersection is approximate since rounded corners are not taking into
+ * account.
+ */
+ nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder);
+ nsRect GetBorderRect() {
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+ nsRect GetPaddingRect() {
+ return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame();
+ }
+ nsRect GetContentRect() {
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ /**
+ * Checks if the frame(s) owning this display item have been marked as invalid,
+ * and needing repainting.
+ */
+ virtual bool IsInvalid(nsRect& aRect) {
+ bool result = mFrame ? mFrame->IsInvalid(aRect) : false;
+ aRect += ToReferenceFrame();
+ return result;
+ }
+
+ /**
+ * Creates and initializes an nsDisplayItemGeometry object that retains the current
+ * areas covered by this display item. These need to retain enough information
+ * such that they can be compared against a future nsDisplayItem of the same type,
+ * and determine if repainting needs to happen.
+ *
+ * Subclasses wishing to store more information need to override both this
+ * and ComputeInvalidationRegion, as well as implementing an nsDisplayItemGeometry
+ * subclass.
+ *
+ * The default implementation tracks both the display item bounds, and the frame's
+ * border rect.
+ */
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder)
+ {
+ return new nsDisplayItemGenericGeometry(this, aBuilder);
+ }
+
+ /**
+ * Compares an nsDisplayItemGeometry object from a previous paint against the
+ * current item. Computes if the geometry of the item has changed, and the
+ * invalidation area required for correct repainting.
+ *
+ * The existing geometry will have been created from a display item with a
+ * matching GetPerFrameKey()/mFrame pair to the current item.
+ *
+ * The default implementation compares the display item bounds, and the frame's
+ * border rect, and invalidates the entire bounds if either rect changes.
+ *
+ * @param aGeometry The geometry of the matching display item from the
+ * previous paint.
+ * @param aInvalidRegion Output param, the region to invalidate, or
+ * unchanged if none.
+ */
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+ {
+ const nsDisplayItemGenericGeometry* geometry = static_cast<const nsDisplayItemGenericGeometry*>(aGeometry);
+ bool snap;
+ if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
+ !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
+ aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
+ }
+ }
+
+ /**
+ * An alternative default implementation of ComputeInvalidationRegion,
+ * that instead invalidates only the changed area between the two items.
+ */
+ void ComputeInvalidationRegionDifference(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemBoundsGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+ {
+ bool snap;
+ nsRect bounds = GetBounds(aBuilder, &snap);
+
+ if (!aGeometry->mBounds.IsEqualInterior(bounds)) {
+ nscoord radii[8];
+ if (aGeometry->mHasRoundedCorners ||
+ Frame()->GetBorderRadii(radii)) {
+ aInvalidRegion->Or(aGeometry->mBounds, bounds);
+ } else {
+ aInvalidRegion->Xor(aGeometry->mBounds, bounds);
+ }
+ }
+ }
+
+ /**
+ * Called when the area rendered by this display item has changed (been
+ * invalidated or changed geometry) since the last paint. This includes
+ * when the display item was not rendered at all in the last paint.
+ * It does NOT get called when a display item was being rendered and no
+ * longer is, because generally that means there is no display item to
+ * call this method on.
+ */
+ virtual void NotifyRenderingChanged() {}
+
+ /**
+ * @param aSnap set to true if the edges of the rectangles of the opaque
+ * region would be snapped to device pixels when drawing
+ * @return a region of the item that is opaque --- that is, every pixel
+ * that is visible is painted with an opaque
+ * color. This is useful for determining when one piece
+ * of content completely obscures another so that we can do occlusion
+ * culling.
+ * This does not take clipping into account.
+ */
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap)
+ {
+ *aSnap = false;
+ return nsRegion();
+ }
+ /**
+ * @return Some(nscolor) if the item is guaranteed to paint every pixel in its
+ * bounds with the same (possibly translucent) color
+ */
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder)
+ { return mozilla::Nothing(); }
+ /**
+ * @return true if the contents of this item are rendered fixed relative
+ * to the nearest viewport.
+ */
+ virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder)
+ { return false; }
+
+ virtual bool ClearsBackground()
+ { return false; }
+
+ virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor)
+ { return false; }
+
+ /**
+ * Returns true if all layers that can be active should be forced to be
+ * active. Requires setting the pref layers.force-active=true.
+ */
+ static bool ForceActiveLayers();
+
+ /**
+ * @return LAYER_NONE if BuildLayer will return null. In this case
+ * there is no layer for the item, and Paint should be called instead
+ * to paint the content using Thebes.
+ * Return LAYER_INACTIVE if there is a layer --- BuildLayer will
+ * not return null (unless there's an error) --- but the layer contents
+ * are not changing frequently. In this case it makes sense to composite
+ * the layer into a PaintedLayer with other content, so we don't have to
+ * recomposite it every time we paint.
+ * Note: GetLayerState is only allowed to return LAYER_INACTIVE if all
+ * descendant display items returned LAYER_INACTIVE or LAYER_NONE. Also,
+ * all descendant display item frames must have an active scrolled root
+ * that's either the same as this item's frame's active scrolled root, or
+ * a descendant of this item's frame. This ensures that the entire
+ * set of display items can be collapsed onto a single PaintedLayer.
+ * Return LAYER_ACTIVE if the layer is active, that is, its contents are
+ * changing frequently. In this case it makes sense to keep the layer
+ * as a separate buffer in VRAM and composite it into the destination
+ * every time we paint.
+ *
+ * Users of GetLayerState should check ForceActiveLayers() and if it returns
+ * true, change a returned value of LAYER_INACTIVE to LAYER_ACTIVE.
+ */
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters)
+ { return mozilla::LAYER_NONE; }
+ /**
+ * Return true to indicate the layer should be constructed even if it's
+ * completely invisible.
+ */
+ virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+ { return false; }
+ /**
+ * Actually paint this item to some rendering context.
+ * Content outside mVisibleRect need not be painted.
+ * aCtx must be set up as for nsDisplayList::Paint.
+ */
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) {}
+
+#ifdef MOZ_DUMP_PAINTING
+ /**
+ * Mark this display item as being painted via FrameLayerBuilder::DrawPaintedLayer.
+ */
+ bool Painted() { return mPainted; }
+
+ /**
+ * Check if this display item has been painted.
+ */
+ void SetPainted() { mPainted = true; }
+#endif
+
+ /**
+ * Get the layer drawn by this display item. Call this only if
+ * GetLayerState() returns something other than LAYER_NONE.
+ * If GetLayerState returned LAYER_NONE then Paint will be called
+ * instead.
+ * This is called while aManager is in the construction phase.
+ *
+ * The caller (nsDisplayList) is responsible for setting the visible
+ * region of the layer.
+ *
+ * @param aContainerParameters should be passed to
+ * FrameLayerBuilder::BuildContainerLayerFor if a ContainerLayer is
+ * constructed.
+ */
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters)
+ { return nullptr; }
+
+ /**
+ * On entry, aVisibleRegion contains the region (relative to ReferenceFrame())
+ * which may be visible. If the display item opaquely covers an area, it
+ * can remove that area from aVisibleRegion before returning.
+ * nsDisplayList::ComputeVisibility automatically subtracts the region
+ * returned by GetOpaqueRegion, and automatically removes items whose bounds
+ * do not intersect the visible area, so implementations of
+ * nsDisplayItem::ComputeVisibility do not need to do these things.
+ * nsDisplayList::ComputeVisibility will already have set mVisibleRect on
+ * this item to the intersection of *aVisibleRegion and this item's bounds.
+ * We rely on that, so this should only be called by
+ * nsDisplayList::ComputeVisibility or nsDisplayItem::RecomputeVisibility.
+ * aAllowVisibleRegionExpansion is a rect where we are allowed to
+ * expand the visible region and is only used for making sure the
+ * background behind a plugin is visible.
+ * This method needs to be idempotent.
+ *
+ * @return true if the item is visible, false if no part of the item
+ * is visible.
+ */
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * Try to merge with the other item (which is below us in the display
+ * list). This gets used by nsDisplayClip to coalesce clipping operations
+ * (optimization), by nsDisplayOpacity to merge rendering for the same
+ * content element into a single opacity group (correctness), and will be
+ * used by nsDisplayOutline to merge multiple outlines for the same element
+ * (also for correctness).
+ * @return true if the merge was successful and the other item should be deleted
+ */
+ virtual bool TryMerge(nsDisplayItem* aItem) {
+ return false;
+ }
+
+ /**
+ * Appends the underlying frames of all display items that have been
+ * merged into this one (excluding this item's own underlying frame)
+ * to aFrames.
+ */
+ virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) {}
+
+ /**
+ * During the visibility computation and after TryMerge, display lists may
+ * return true here to flatten themselves away, removing them. This
+ * flattening is distinctly different from FlattenTo, which occurs before
+ * items are merged together.
+ */
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+ return false;
+ }
+
+ /**
+ * If this has a child list where the children are in the same coordinate
+ * system as this item (i.e., they have the same reference frame),
+ * return the list.
+ */
+ virtual nsDisplayList* GetSameCoordinateSystemChildren() { return nullptr; }
+ virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) {}
+ /**
+ * Do UpdateBounds() for items with frames establishing or extending
+ * 3D rendering context.
+ *
+ * This function is called by UpdateBoundsFor3D() of
+ * nsDisplayTransform(), and it is called by
+ * BuildDisplayListForStackingContext() on transform items
+ * establishing 3D rendering context.
+ *
+ * The bounds of a transform item with the frame establishing 3D
+ * rendering context should be computed by calling
+ * DoUpdateBoundsPreserves3D() on all descendants that participate
+ * the same 3d rendering context.
+ */
+ virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) {}
+
+ /**
+ * If this has a child list, return it, even if the children are in
+ * a different coordinate system to this item.
+ */
+ virtual nsDisplayList* GetChildren() { return nullptr; }
+
+ /**
+ * Returns the visible rect.
+ */
+ const nsRect& GetVisibleRect() const { return mVisibleRect; }
+
+ /**
+ * Returns the visible rect for the children, relative to their
+ * reference frame. Can be different from mVisibleRect for nsDisplayTransform,
+ * since the reference frame for the children is different from the reference
+ * frame for the item itself.
+ */
+ virtual const nsRect& GetVisibleRectForChildren() const { return mVisibleRect; }
+
+ /**
+ * Stores the given opacity value to be applied when drawing. It is an error to
+ * call this if CanApplyOpacity returned false.
+ */
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip) {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity not supported on this type");
+ }
+ /**
+ * Returns true if this display item would return true from ApplyOpacity without
+ * actually applying the opacity. Otherwise returns false.
+ */
+ virtual bool CanApplyOpacity() const {
+ return false;
+ }
+
+ /**
+ * For debugging and stuff
+ */
+ virtual const char* Name() = 0;
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) {}
+
+ nsDisplayItem* GetAbove() { return mAbove; }
+
+ /**
+ * Like ComputeVisibility, but does the work that nsDisplayList
+ * does per-item:
+ * -- Intersects GetBounds with aVisibleRegion and puts the result
+ * in mVisibleRect
+ * -- Subtracts bounds from aVisibleRegion if the item is opaque
+ */
+ bool RecomputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame())
+ */
+ const nsPoint& ToReferenceFrame() const {
+ NS_ASSERTION(mFrame, "No frame?");
+ return mToReferenceFrame;
+ }
+ /**
+ * @return the root of the display list's frame (sub)tree, whose origin
+ * establishes the coordinate system for the display list
+ */
+ const nsIFrame* ReferenceFrame() const { return mReferenceFrame; }
+
+ /**
+ * Returns the reference frame for display item children of this item.
+ */
+ virtual const nsIFrame* ReferenceFrameForChildren() const { return mReferenceFrame; }
+
+ AnimatedGeometryRoot* GetAnimatedGeometryRoot() const {
+ MOZ_ASSERT(mAnimatedGeometryRoot, "Must have cached AGR before accessing it!");
+ return mAnimatedGeometryRoot;
+ }
+
+ virtual struct AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const {
+ return GetAnimatedGeometryRoot();
+ }
+
+ /**
+ * Checks if this display item (or any children) contains content that might
+ * be rendered with component alpha (e.g. subpixel antialiasing). Returns the
+ * bounds of the area that needs component alpha, or an empty rect if nothing
+ * in the item does.
+ */
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) { return nsRect(); }
+
+ /**
+ * Disable usage of component alpha. Currently only relevant for items that have text.
+ */
+ virtual void DisableComponentAlpha() {}
+
+ /**
+ * Check if we can add async animations to the layer for this display item.
+ */
+ virtual bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
+ return false;
+ }
+
+ virtual bool SupportsOptimizingToImage() { return false; }
+
+ const DisplayItemClip& GetClip()
+ {
+ return mClip ? *mClip : DisplayItemClip::NoClip();
+ }
+ void SetClip(nsDisplayListBuilder* aBuilder, const DisplayItemClip& aClip)
+ {
+ if (!aClip.HasClip()) {
+ mClip = nullptr;
+ return;
+ }
+ mClip = aBuilder->AllocateDisplayItemClip(aClip);
+ }
+
+ void IntersectClip(nsDisplayListBuilder* aBuilder, const DisplayItemClip& aClip)
+ {
+ if (mClip) {
+ DisplayItemClip temp = *mClip;
+ temp.IntersectWith(aClip);
+ SetClip(aBuilder, temp);
+ } else {
+ SetClip(aBuilder, aClip);
+ }
+ }
+
+ void SetScrollClip(const DisplayItemScrollClip* aScrollClip) { mScrollClip = aScrollClip; }
+ const DisplayItemScrollClip* ScrollClip() const { return mScrollClip; }
+
+ bool BackfaceIsHidden() {
+ return mFrame->BackfaceIsHidden();
+ }
+
+protected:
+ friend class nsDisplayList;
+
+ nsDisplayItem() { mAbove = nullptr; }
+
+ nsIFrame* mFrame;
+ const DisplayItemClip* mClip;
+ const DisplayItemScrollClip* mScrollClip;
+ // Result of FindReferenceFrameFor(mFrame), if mFrame is non-null
+ const nsIFrame* mReferenceFrame;
+ struct AnimatedGeometryRoot* mAnimatedGeometryRoot;
+ // Result of ToReferenceFrame(mFrame), if mFrame is non-null
+ nsPoint mToReferenceFrame;
+ // This is the rectangle that needs to be painted.
+ // Display item construction sets this to the dirty rect.
+ // nsDisplayList::ComputeVisibility sets this to the visible region
+ // of the item by intersecting the current visible region with the bounds
+ // of the item. Paint implementations can use this to limit their drawing.
+ // Guaranteed to be contained in GetBounds().
+ nsRect mVisibleRect;
+ bool mForceNotVisible;
+#ifdef MOZ_DUMP_PAINTING
+ // True if this frame has been painted.
+ bool mPainted;
+#endif
+};
+
+/**
+ * Manages a singly-linked list of display list items.
+ *
+ * mSentinel is the sentinel list value, the first value in the null-terminated
+ * linked list of items. mTop is the last item in the list (whose 'above'
+ * pointer is null). This class has no virtual methods. So list objects are just
+ * two pointers.
+ *
+ * Stepping upward through this list is very fast. Stepping downward is very
+ * slow so we don't support it. The methods that need to step downward
+ * (HitTest(), ComputeVisibility()) internally build a temporary array of all
+ * the items while they do the downward traversal, so overall they're still
+ * linear time. We have optimized for efficient AppendToTop() of both
+ * items and lists, with minimal codesize. AppendToBottom() is efficient too.
+ */
+class nsDisplayList {
+public:
+ typedef mozilla::DisplayItemScrollClip DisplayItemScrollClip;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::PaintedLayer PaintedLayer;
+
+ /**
+ * Create an empty list.
+ */
+ nsDisplayList()
+ : mIsOpaque(false)
+ , mForceTransparentSurface(false)
+ {
+ mTop = &mSentinel;
+ mSentinel.mAbove = nullptr;
+ }
+ ~nsDisplayList() {
+ if (mSentinel.mAbove) {
+ NS_WARNING("Nonempty list left over?");
+ }
+ DeleteAll();
+ }
+
+ /**
+ * Append an item to the top of the list. The item must not currently
+ * be in a list and cannot be null.
+ */
+ void AppendToTop(nsDisplayItem* aItem) {
+ NS_ASSERTION(aItem, "No item to append!");
+ NS_ASSERTION(!aItem->mAbove, "Already in a list!");
+ mTop->mAbove = aItem;
+ mTop = aItem;
+ }
+
+ /**
+ * Append a new item to the top of the list. The intended usage is
+ * AppendNewToTop(new ...);
+ */
+ void AppendNewToTop(nsDisplayItem* aItem) {
+ if (aItem) {
+ AppendToTop(aItem);
+ }
+ }
+
+ /**
+ * Append a new item to the bottom of the list. The intended usage is
+ * AppendNewToBottom(new ...);
+ */
+ void AppendNewToBottom(nsDisplayItem* aItem) {
+ if (aItem) {
+ AppendToBottom(aItem);
+ }
+ }
+
+ /**
+ * Append a new item to the bottom of the list. The item must be non-null
+ * and not already in a list.
+ */
+ void AppendToBottom(nsDisplayItem* aItem) {
+ NS_ASSERTION(aItem, "No item to append!");
+ NS_ASSERTION(!aItem->mAbove, "Already in a list!");
+ aItem->mAbove = mSentinel.mAbove;
+ mSentinel.mAbove = aItem;
+ if (mTop == &mSentinel) {
+ mTop = aItem;
+ }
+ }
+
+ /**
+ * Removes all items from aList and appends them to the top of this list
+ */
+ void AppendToTop(nsDisplayList* aList) {
+ if (aList->mSentinel.mAbove) {
+ mTop->mAbove = aList->mSentinel.mAbove;
+ mTop = aList->mTop;
+ aList->mTop = &aList->mSentinel;
+ aList->mSentinel.mAbove = nullptr;
+ }
+ }
+
+ /**
+ * Removes all items from aList and prepends them to the bottom of this list
+ */
+ void AppendToBottom(nsDisplayList* aList) {
+ if (aList->mSentinel.mAbove) {
+ aList->mTop->mAbove = mSentinel.mAbove;
+ mSentinel.mAbove = aList->mSentinel.mAbove;
+ if (mTop == &mSentinel) {
+ mTop = aList->mTop;
+ }
+
+ aList->mTop = &aList->mSentinel;
+ aList->mSentinel.mAbove = nullptr;
+ }
+ }
+
+ /**
+ * Remove an item from the bottom of the list and return it.
+ */
+ nsDisplayItem* RemoveBottom();
+
+ /**
+ * Remove all items from the list and call their destructors.
+ */
+ void DeleteAll();
+
+ /**
+ * @return the item at the top of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetTop() const {
+ return mTop != &mSentinel ? static_cast<nsDisplayItem*>(mTop) : nullptr;
+ }
+ /**
+ * @return the item at the bottom of the list, or null if the list is empty
+ */
+ nsDisplayItem* GetBottom() const { return mSentinel.mAbove; }
+ bool IsEmpty() const { return mTop == &mSentinel; }
+
+ /**
+ * This is *linear time*!
+ * @return the number of items in the list
+ */
+ uint32_t Count() const;
+ /**
+ * Stable sort the list by the z-order of GetUnderlyingFrame() on
+ * each item. 'auto' is counted as zero.
+ * It is assumed that the list is already in content document order.
+ */
+ void SortByZOrder();
+ /**
+ * Stable sort the list by the tree order of the content of
+ * GetUnderlyingFrame() on each item. z-index is ignored.
+ * @param aCommonAncestor a common ancestor of all the content elements
+ * associated with the display items, for speeding up tree order
+ * checks, or nullptr if not known; it's only a hint, if it is not an
+ * ancestor of some elements, then we lose performance but not correctness
+ */
+ void SortByContentOrder(nsIContent* aCommonAncestor);
+
+ /**
+ * Generic stable sort. Take care, because some of the items might be nsDisplayLists
+ * themselves.
+ * aCmp(item1, item2) should return true if item1 <= item2. We sort the items
+ * into increasing order.
+ */
+ typedef bool (* SortLEQ)(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
+ void* aClosure);
+ void Sort(SortLEQ aCmp, void* aClosure);
+
+ /**
+ * Compute visiblity for the items in the list.
+ * We put this logic here so it can be shared by top-level
+ * painting and also display items that maintain child lists.
+ * This is also a good place to put ComputeVisibility-related logic
+ * that must be applied to every display item. In particular, this
+ * sets mVisibleRect on each display item.
+ * This sets mIsOpaque if the entire visible area of this list has
+ * been removed from aVisibleRegion when we return.
+ * This does not remove any items from the list, so we can recompute
+ * visiblity with different regions later (see
+ * FrameLayerBuilder::DrawPaintedLayer).
+ * This method needs to be idempotent.
+ *
+ * @param aVisibleRegion the area that is visible, relative to the
+ * reference frame; on return, this contains the area visible under the list.
+ * I.e., opaque contents of this list are subtracted from aVisibleRegion.
+ * @param aListVisibleBounds must be equal to the bounds of the intersection
+ * of aVisibleRegion and GetBounds() for this list.
+ * @return true if any item in the list is visible.
+ */
+ bool ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion,
+ const nsRect& aListVisibleBounds);
+
+ /**
+ * As ComputeVisibilityForSublist, but computes visibility for a root
+ * list (a list that does not belong to an nsDisplayItem).
+ * This method needs to be idempotent.
+ *
+ * @param aVisibleRegion the area that is visible
+ */
+ bool ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion);
+
+ /**
+ * Returns true if the visible region output from ComputeVisiblity was
+ * empty, i.e. everything visible in this list is opaque.
+ */
+ bool IsOpaque() const {
+ return mIsOpaque;
+ }
+
+ /**
+ * Returns true if any display item requires the surface to be transparent.
+ */
+ bool NeedsTransparentSurface() const {
+ return mForceTransparentSurface;
+ }
+ /**
+ * Paint the list to the rendering context. We assume that (0,0) in aCtx
+ * corresponds to the origin of the reference frame. For best results,
+ * aCtx's current transform should make (0,0) pixel-aligned. The
+ * rectangle in aDirtyRect is painted, which *must* be contained in the
+ * dirty rect used to construct the display list.
+ *
+ * If aFlags contains PAINT_USE_WIDGET_LAYERS and
+ * ShouldUseWidgetLayerManager() is set, then we will paint using
+ * the reference frame's widget's layer manager (and ctx may be null),
+ * otherwise we will use a temporary BasicLayerManager and ctx must
+ * not be null.
+ *
+ * If PAINT_EXISTING_TRANSACTION is set, the reference frame's widget's
+ * layer manager has already had BeginTransaction() called on it and
+ * we should not call it again.
+ *
+ * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to compressed mode
+ * to avoid short cut optimizations.
+ *
+ * This must only be called on the root display list of the display list
+ * tree.
+ *
+ * We return the layer manager used for painting --- mainly so that
+ * callers can dump its layer tree if necessary.
+ */
+ enum {
+ PAINT_DEFAULT = 0,
+ PAINT_USE_WIDGET_LAYERS = 0x01,
+ PAINT_EXISTING_TRANSACTION = 0x04,
+ PAINT_NO_COMPOSITE = 0x08,
+ PAINT_COMPRESSED = 0x10
+ };
+ already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ uint32_t aFlags);
+ /**
+ * Get the bounds. Takes the union of the bounds of all children.
+ * The result is not cached.
+ */
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder) const;
+ /**
+ * Return the union of the scroll clipped bounds of all children. To get the
+ * scroll clipped bounds of a child item, we start with the item's clipped
+ * bounds and walk its scroll clip chain up to (but not including)
+ * aIncludeScrollClipsUpTo, and take each scroll clip into account. For
+ * scroll clips from async scrollable frames we assume that the item can move
+ * anywhere inside that scroll frame.
+ * In other words, the return value from this method includes all pixels that
+ * could potentially be covered by items in this list under async scrolling.
+ */
+ nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
+ const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const;
+ /**
+ * Find the topmost display item that returns a non-null frame, and return
+ * the frame.
+ */
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nsDisplayItem::HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) const;
+ /**
+ * Compute the union of the visible rects of the items in the list. The
+ * result is not cached.
+ */
+ nsRect GetVisibleRect() const;
+
+ void SetIsOpaque()
+ {
+ mIsOpaque = true;
+ }
+ void SetNeedsTransparentSurface()
+ {
+ mForceTransparentSurface = true;
+ }
+
+private:
+ // This class is only used on stack, so we don't have to worry about leaking
+ // it. Don't let us be heap-allocated!
+ void* operator new(size_t sz) CPP_THROW_NEW;
+
+ nsDisplayItemLink mSentinel;
+ nsDisplayItemLink* mTop;
+
+ // This is set to true by FrameLayerBuilder if the final visible region
+ // is empty (i.e. everything that was visible is covered by some
+ // opaque content in this list).
+ bool mIsOpaque;
+ // This is set to true by FrameLayerBuilder if any display item in this
+ // list needs to force the surface containing this list to be transparent.
+ bool mForceTransparentSurface;
+};
+
+/**
+ * This is passed as a parameter to nsIFrame::BuildDisplayList. That method
+ * will put any generated items onto the appropriate list given here. It's
+ * basically just a collection with one list for each separate stacking layer.
+ * The lists themselves are external to this object and thus can be shared
+ * with others. Some of the list pointers may even refer to the same list.
+ */
+class nsDisplayListSet {
+public:
+ /**
+ * @return a list where one should place the border and/or background for
+ * this frame (everything from steps 1 and 2 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* BorderBackground() const { return mBorderBackground; }
+ /**
+ * @return a list where one should place the borders and/or backgrounds for
+ * block-level in-flow descendants (step 4 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* BlockBorderBackgrounds() const { return mBlockBorderBackgrounds; }
+ /**
+ * @return a list where one should place descendant floats (step 5 of
+ * CSS 2.1 appendix E)
+ */
+ nsDisplayList* Floats() const { return mFloats; }
+ /**
+ * @return a list where one should place the (pseudo) stacking contexts
+ * for descendants of this frame (everything from steps 3, 7 and 8
+ * of CSS 2.1 appendix E)
+ */
+ nsDisplayList* PositionedDescendants() const { return mPositioned; }
+ /**
+ * @return a list where one should place the outlines
+ * for this frame and its descendants (step 9 of CSS 2.1 appendix E)
+ */
+ nsDisplayList* Outlines() const { return mOutlines; }
+ /**
+ * @return a list where one should place all other content
+ */
+ nsDisplayList* Content() const { return mContent; }
+
+ nsDisplayListSet(nsDisplayList* aBorderBackground,
+ nsDisplayList* aBlockBorderBackgrounds,
+ nsDisplayList* aFloats,
+ nsDisplayList* aContent,
+ nsDisplayList* aPositionedDescendants,
+ nsDisplayList* aOutlines) :
+ mBorderBackground(aBorderBackground),
+ mBlockBorderBackgrounds(aBlockBorderBackgrounds),
+ mFloats(aFloats),
+ mContent(aContent),
+ mPositioned(aPositionedDescendants),
+ mOutlines(aOutlines) {
+ }
+
+ /**
+ * A copy constructor that lets the caller override the BorderBackground
+ * list.
+ */
+ nsDisplayListSet(const nsDisplayListSet& aLists,
+ nsDisplayList* aBorderBackground) :
+ mBorderBackground(aBorderBackground),
+ mBlockBorderBackgrounds(aLists.BlockBorderBackgrounds()),
+ mFloats(aLists.Floats()),
+ mContent(aLists.Content()),
+ mPositioned(aLists.PositionedDescendants()),
+ mOutlines(aLists.Outlines()) {
+ }
+
+ /**
+ * Move all display items in our lists to top of the corresponding lists in the
+ * destination.
+ */
+ void MoveTo(const nsDisplayListSet& aDestination) const;
+
+private:
+ // This class is only used on stack, so we don't have to worry about leaking
+ // it. Don't let us be heap-allocated!
+ void* operator new(size_t sz) CPP_THROW_NEW;
+
+protected:
+ nsDisplayList* mBorderBackground;
+ nsDisplayList* mBlockBorderBackgrounds;
+ nsDisplayList* mFloats;
+ nsDisplayList* mContent;
+ nsDisplayList* mPositioned;
+ nsDisplayList* mOutlines;
+};
+
+/**
+ * A specialization of nsDisplayListSet where the lists are actually internal
+ * to the object, and all distinct.
+ */
+struct nsDisplayListCollection : public nsDisplayListSet {
+ nsDisplayListCollection() :
+ nsDisplayListSet(&mLists[0], &mLists[1], &mLists[2], &mLists[3], &mLists[4],
+ &mLists[5]) {}
+ explicit nsDisplayListCollection(nsDisplayList* aBorderBackground) :
+ nsDisplayListSet(aBorderBackground, &mLists[1], &mLists[2], &mLists[3], &mLists[4],
+ &mLists[5]) {}
+
+ /**
+ * Sort all lists by content order.
+ */
+ void SortAllByContentOrder(nsIContent* aCommonAncestor) {
+ for (int32_t i = 0; i < 6; ++i) {
+ mLists[i].SortByContentOrder(aCommonAncestor);
+ }
+ }
+
+private:
+ // This class is only used on stack, so we don't have to worry about leaking
+ // it. Don't let us be heap-allocated!
+ void* operator new(size_t sz) CPP_THROW_NEW;
+
+ nsDisplayList mLists[6];
+};
+
+
+class nsDisplayImageContainer : public nsDisplayItem {
+public:
+ typedef mozilla::LayerIntPoint LayerIntPoint;
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::ImageLayer ImageLayer;
+
+ nsDisplayImageContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ {}
+
+ /**
+ * @return true if this display item can be optimized into an image layer.
+ * It is an error to call GetContainer() unless you've called
+ * CanOptimizeToImageLayer() first and it returned true.
+ */
+ virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+
+ already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+ void ConfigureLayer(ImageLayer* aLayer,
+ const ContainerLayerParameters& aParameters);
+
+ virtual already_AddRefed<imgIContainer> GetImage() = 0;
+
+ virtual nsRect GetDestRect() = 0;
+
+ virtual bool SupportsOptimizingToImage() override { return true; }
+};
+
+/**
+ * Use this class to implement not-very-frequently-used display items
+ * that are not opaque, do not receive events, and are bounded by a frame's
+ * border-rect.
+ *
+ * This should not be used for display items which are created frequently,
+ * because each item is one or two pointers bigger than an item from a
+ * custom display item class could be, and fractionally slower. However it does
+ * save code size. We use this for infrequently-used item types.
+ */
+class nsDisplayGeneric : public nsDisplayItem {
+public:
+ typedef class mozilla::gfx::DrawTarget DrawTarget;
+
+ typedef void (* PaintCallback)(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect, nsPoint aFramePt);
+
+ // XXX: should be removed eventually
+ typedef void (* OldPaintCallback)(nsIFrame* aFrame, nsRenderingContext* aCtx,
+ const nsRect& aDirtyRect, nsPoint aFramePt);
+
+ nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ PaintCallback aPaint, const char* aName, Type aType)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mPaint(aPaint)
+ , mOldPaint(nullptr)
+ , mName(aName)
+ , mType(aType)
+ {
+ MOZ_COUNT_CTOR(nsDisplayGeneric);
+ }
+
+ // XXX: should be removed eventually
+ nsDisplayGeneric(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ OldPaintCallback aOldPaint, const char* aName, Type aType)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mPaint(nullptr)
+ , mOldPaint(aOldPaint)
+ , mName(aName)
+ , mType(aType)
+ {
+ MOZ_COUNT_CTOR(nsDisplayGeneric);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayGeneric() {
+ MOZ_COUNT_DTOR(nsDisplayGeneric);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override {
+ MOZ_ASSERT(!!mPaint != !!mOldPaint);
+ if (mPaint) {
+ mPaint(mFrame, aCtx->GetDrawTarget(), mVisibleRect, ToReferenceFrame());
+ } else {
+ mOldPaint(mFrame, aCtx, mVisibleRect, ToReferenceFrame());
+ }
+ }
+ NS_DISPLAY_DECL_NAME(mName, mType)
+
+protected:
+ PaintCallback mPaint;
+ OldPaintCallback mOldPaint; // XXX: should be removed eventually
+ const char* mName;
+ Type mType;
+};
+
+/**
+ * Generic display item that can contain overflow. Use this in lieu of
+ * nsDisplayGeneric if you have a frame that should use the visual overflow
+ * rect of its frame when drawing items, instead of the frame's bounds.
+ */
+class nsDisplayGenericOverflow : public nsDisplayGeneric {
+ public:
+ nsDisplayGenericOverflow(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ PaintCallback aPaint, const char* aName, Type aType)
+ : nsDisplayGeneric(aBuilder, aFrame, aPaint, aName, aType)
+ {
+ MOZ_COUNT_CTOR(nsDisplayGenericOverflow);
+ }
+ // XXX: should be removed eventually
+ nsDisplayGenericOverflow(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ OldPaintCallback aOldPaint, const char* aName, Type aType)
+ : nsDisplayGeneric(aBuilder, aFrame, aOldPaint, aName, aType)
+ {
+ MOZ_COUNT_CTOR(nsDisplayGenericOverflow);
+ }
+ #ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayGenericOverflow() {
+ MOZ_COUNT_DTOR(nsDisplayGenericOverflow);
+ }
+ #endif
+
+ /**
+ * Returns the frame's visual overflow rect instead of the frame's bounds.
+ */
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override
+ {
+ *aSnap = false;
+ return Frame()->GetVisualOverflowRect() + ToReferenceFrame();
+ }
+};
+
+#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
+/**
+ * This class implements painting of reflow counts. Ideally, we would simply
+ * make all the frame names be those returned by nsFrame::GetFrameName
+ * (except that tosses in the content tag name!) and support only one color
+ * and eliminate this class altogether in favor of nsDisplayGeneric, but for
+ * the time being we can't pass args to a PaintCallback, so just have a
+ * separate class to do the right thing. Sadly, this alsmo means we need to
+ * hack all leaf frame classes to handle this.
+ *
+ * XXXbz the color thing is a bit of a mess, but 0 basically means "not set"
+ * here... I could switch it all to nscolor, but why bother?
+ */
+class nsDisplayReflowCount : public nsDisplayItem {
+public:
+ nsDisplayReflowCount(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const char* aFrameName,
+ uint32_t aColor = 0)
+ : nsDisplayItem(aBuilder, aFrame),
+ mFrameName(aFrameName),
+ mColor(aColor)
+ {
+ MOZ_COUNT_CTOR(nsDisplayReflowCount);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayReflowCount() {
+ MOZ_COUNT_DTOR(nsDisplayReflowCount);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override {
+ mFrame->PresContext()->PresShell()->PaintCount(mFrameName, aCtx,
+ mFrame->PresContext(),
+ mFrame, ToReferenceFrame(),
+ mColor);
+ }
+ NS_DISPLAY_DECL_NAME("nsDisplayReflowCount", TYPE_REFLOW_COUNT)
+protected:
+ const char* mFrameName;
+ nscolor mColor;
+};
+
+#define DO_GLOBAL_REFLOW_COUNT_DSP(_name) \
+ PR_BEGIN_MACRO \
+ if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \
+ PresContext()->PresShell()->IsPaintingFrameCounts()) { \
+ aLists.Outlines()->AppendNewToTop( \
+ new (aBuilder) nsDisplayReflowCount(aBuilder, this, _name)); \
+ } \
+ PR_END_MACRO
+
+#define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color) \
+ PR_BEGIN_MACRO \
+ if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() && \
+ PresContext()->PresShell()->IsPaintingFrameCounts()) { \
+ aLists.Outlines()->AppendNewToTop( \
+ new (aBuilder) nsDisplayReflowCount(aBuilder, this, _name, _color)); \
+ } \
+ PR_END_MACRO
+
+/*
+ Macro to be used for classes that don't actually implement BuildDisplayList
+ */
+#define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super) \
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder, \
+ const nsRect& aDirtyRect, \
+ const nsDisplayListSet& aLists) { \
+ DO_GLOBAL_REFLOW_COUNT_DSP(#_class); \
+ _super::BuildDisplayList(aBuilder, aDirtyRect, aLists); \
+ }
+
+#else // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF
+
+#define DO_GLOBAL_REFLOW_COUNT_DSP(_name)
+#define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color)
+#define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super)
+
+#endif // MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF
+
+class nsDisplayCaret : public nsDisplayItem {
+public:
+ nsDisplayCaret(nsDisplayListBuilder* aBuilder, nsIFrame* aCaretFrame);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayCaret();
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("Caret", TYPE_CARET)
+
+protected:
+ RefPtr<nsCaret> mCaret;
+ nsRect mBounds;
+};
+
+/**
+ * The standard display item to paint the CSS borders of a frame.
+ */
+class nsDisplayBorder : public nsDisplayItem {
+public:
+ nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayBorder() {
+ MOZ_COUNT_DTOR(nsDisplayBorder);
+ }
+#endif
+
+ virtual bool IsInvisibleInRect(const nsRect& aRect) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER)
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+protected:
+ nsRect CalculateBounds(const nsStyleBorder& aStyleBorder);
+
+ nsRect mBounds;
+};
+
+/**
+ * A simple display item that just renders a solid color across the
+ * specified bounds. For canvas frames (in the CSS sense) we split off the
+ * drawing of the background color into this class (from nsDisplayBackground
+ * via nsDisplayCanvasBackground). This is done so that we can always draw a
+ * background color to avoid ugly flashes of white when we can't draw a full
+ * frame tree (ie when a page is loading). The bounds can differ from the
+ * frame's bounds -- this is needed when a frame/iframe is loading and there
+ * is not yet a frame tree to go in the frame/iframe so we use the subdoc
+ * frame of the parent document as a standin.
+ */
+class nsDisplaySolidColorBase : public nsDisplayItem {
+public:
+ nsDisplaySolidColorBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nscolor aColor)
+ : nsDisplayItem(aBuilder, aFrame), mColor(aColor)
+ {}
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplaySolidColorGeometry(this, aBuilder, mColor);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ const nsDisplaySolidColorGeometry* geometry =
+ static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
+ if (mColor != geometry->mColor) {
+ bool dummy;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
+ return;
+ }
+ ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
+ }
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override {
+ *aSnap = false;
+ nsRegion result;
+ if (NS_GET_A(mColor) == 255) {
+ result = GetBounds(aBuilder, aSnap);
+ }
+ return result;
+ }
+
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+ {
+ return mozilla::Some(mColor);
+ }
+
+protected:
+ nscolor mColor;
+};
+
+class nsDisplaySolidColor : public nsDisplaySolidColorBase {
+public:
+ nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBounds, nscolor aColor)
+ : nsDisplaySolidColorBase(aBuilder, aFrame, aColor), mBounds(aBounds)
+ {
+ NS_ASSERTION(NS_GET_A(aColor) > 0, "Don't create invisible nsDisplaySolidColors!");
+ MOZ_COUNT_CTOR(nsDisplaySolidColor);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySolidColor() {
+ MOZ_COUNT_DTOR(nsDisplaySolidColor);
+ }
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+ NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR)
+
+private:
+ nsRect mBounds;
+};
+
+/**
+ * A display item that renders a solid color over a region. This is not
+ * exposed through CSS, its only purpose is efficient invalidation of
+ * the find bar highlighter dimmer.
+ */
+class nsDisplaySolidColorRegion : public nsDisplayItem {
+ typedef mozilla::gfx::Color Color;
+
+public:
+ nsDisplaySolidColorRegion(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRegion& aRegion, nscolor aColor)
+ : nsDisplayItem(aBuilder, aFrame), mRegion(aRegion), mColor(Color::FromABGR(aColor))
+ {
+ NS_ASSERTION(NS_GET_A(aColor) > 0, "Don't create invisible nsDisplaySolidColorRegions!");
+ MOZ_COUNT_CTOR(nsDisplaySolidColorRegion);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySolidColorRegion() {
+ MOZ_COUNT_DTOR(nsDisplaySolidColorRegion);
+ }
+#endif
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplaySolidColorRegionGeometry(this, aBuilder, mRegion, mColor);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ const nsDisplaySolidColorRegionGeometry* geometry =
+ static_cast<const nsDisplaySolidColorRegionGeometry*>(aGeometry);
+ if (mColor == geometry->mColor) {
+ aInvalidRegion->Xor(geometry->mRegion, mRegion);
+ } else {
+ aInvalidRegion->Or(geometry->mRegion.GetBounds(), mRegion.GetBounds());
+ }
+ }
+
+protected:
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+ NS_DISPLAY_DECL_NAME("SolidColorRegion", TYPE_SOLID_COLOR_REGION)
+
+private:
+ nsRegion mRegion;
+ Color mColor;
+};
+
+/**
+ * A display item to paint one background-image for a frame. Each background
+ * image layer gets its own nsDisplayBackgroundImage.
+ */
+class nsDisplayBackgroundImage : public nsDisplayImageContainer {
+public:
+ /**
+ * aLayer signifies which background layer this item represents.
+ * aIsThemed should be the value of aFrame->IsThemed.
+ * aBackgroundStyle should be the result of
+ * nsCSSRendering::FindBackground, or null if FindBackground returned false.
+ * aBackgroundRect is relative to aFrame.
+ */
+ nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ uint32_t aLayer, const nsRect& aBackgroundRect,
+ const nsStyleBackground* aBackgroundStyle);
+ virtual ~nsDisplayBackgroundImage();
+
+ // This will create and append new items for all the layers of the
+ // background. Returns whether we appended a themed background.
+ // aAllowWillPaintBorderOptimization should usually be left at true, unless
+ // aFrame has special border drawing that causes opaque borders to not
+ // actually be opaque.
+ static bool AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ nsDisplayList* aList,
+ bool aAllowWillPaintBorderOptimization = true,
+ nsStyleContext* aStyleContext = nullptr);
+
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+ /**
+ * GetBounds() returns the background painting area.
+ */
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ virtual uint32_t GetPerFrameKey() override;
+ NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND)
+
+ /**
+ * Return the background positioning area.
+ * (GetBounds() returns the background painting area.)
+ * Can be called only when mBackgroundStyle is non-null.
+ */
+ nsRect GetPositioningArea();
+
+ /**
+ * Returns true if existing rendered pixels of this display item may need
+ * to be redrawn if the positioning area size changes but its position does
+ * not.
+ * If false, only the changed painting area needs to be redrawn when the
+ * positioning area size changes but its position does not.
+ */
+ bool RenderingMightDependOnPositioningAreaSizeChange();
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayBackgroundGeometry(this, aBuilder);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+ virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
+ nsDisplayListBuilder* aBuilder) override;
+ virtual already_AddRefed<imgIContainer> GetImage() override;
+ virtual nsRect GetDestRect() override;
+
+ static nsRegion GetInsideClipRegion(nsDisplayItem* aItem, uint8_t aClip,
+ const nsRect& aRect, const nsRect& aBackgroundRect);
+
+ virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override;
+
+protected:
+ typedef class mozilla::layers::ImageContainer ImageContainer;
+ typedef class mozilla::layers::ImageLayer ImageLayer;
+
+ bool TryOptimizeToImageLayer(LayerManager* aManager, nsDisplayListBuilder* aBuilder);
+ bool IsNonEmptyFixedImage() const;
+ nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder);
+ bool ShouldTreatAsFixed() const;
+ bool ComputeShouldTreatAsFixed(bool isTransformedFixed) const;
+
+ void PaintInternal(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx,
+ const nsRect& aBounds, nsRect* aClipRect);
+
+ // Determine whether we want to be separated into our own layer, independent
+ // of whether this item can actually be layerized.
+ enum ImageLayerization {
+ WHENEVER_POSSIBLE,
+ ONLY_FOR_SCALING,
+ NO_LAYER_NEEDED
+ };
+ ImageLayerization ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager);
+
+ // Cache the result of nsCSSRendering::FindBackground. Always null if
+ // mIsThemed is true or if FindBackground returned false.
+ const nsStyleBackground* mBackgroundStyle;
+ nsCOMPtr<imgIContainer> mImage;
+ nsRect mBackgroundRect; // relative to the reference frame
+ nsRect mFillRect;
+ nsRect mDestRect;
+ /* Bounds of this display item */
+ nsRect mBounds;
+ uint32_t mLayer;
+ bool mIsRasterImage;
+ /* Whether the image should be treated as fixed to the viewport. */
+ bool mShouldTreatAsFixed;
+};
+
+
+/**
+ * A display item to paint the native theme background for a frame.
+ */
+class nsDisplayThemedBackground : public nsDisplayItem {
+public:
+ nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect);
+ virtual ~nsDisplayThemedBackground();
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+ virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor) override;
+
+ /**
+ * GetBounds() returns the background painting area.
+ */
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("ThemedBackground", TYPE_THEMED_BACKGROUND)
+
+ /**
+ * Return the background positioning area.
+ * (GetBounds() returns the background painting area.)
+ * Can be called only when mBackgroundStyle is non-null.
+ */
+ nsRect GetPositioningArea();
+
+ /**
+ * Return whether our frame's document does not have the state
+ * NS_DOCUMENT_STATE_WINDOW_INACTIVE.
+ */
+ bool IsWindowActive();
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayThemedBackgroundGeometry(this, aBuilder);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+protected:
+ nsRect GetBoundsInternal();
+
+ void PaintInternal(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx,
+ const nsRect& aBounds, nsRect* aClipRect);
+
+ nsRect mBackgroundRect;
+ nsRect mBounds;
+ nsITheme::Transparency mThemeTransparency;
+ uint8_t mAppearance;
+};
+
+class nsDisplayBackgroundColor : public nsDisplayItem
+{
+ typedef mozilla::gfx::Color Color;
+
+public:
+ nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aBackgroundRect,
+ const nsStyleBackground* aBackgroundStyle,
+ nscolor aColor)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mBackgroundRect(aBackgroundRect)
+ , mBackgroundStyle(aBackgroundStyle)
+ , mColor(Color::FromABGR(aColor))
+ { }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip) override;
+ virtual bool CanApplyOpacity() const override;
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ *aSnap = true;
+ return mBackgroundRect;
+ }
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplaySolidColorGeometry(this, aBuilder, mColor.ToABGR());
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ const nsDisplaySolidColorGeometry* geometry = static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
+ if (mColor.ToABGR() != geometry->mColor) {
+ bool dummy;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
+ return;
+ }
+ ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
+ }
+
+ NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR)
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+protected:
+ const nsRect mBackgroundRect;
+ const nsStyleBackground* mBackgroundStyle;
+ mozilla::gfx::Color mColor;
+};
+
+class nsDisplayClearBackground : public nsDisplayItem
+{
+public:
+ nsDisplayClearBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ { }
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ *aSnap = true;
+ return nsRect(ToReferenceFrame(), Frame()->GetSize());
+ }
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override {
+ *aSnap = false;
+ return GetBounds(aBuilder, aSnap);
+ }
+
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+ {
+ return mozilla::Some(NS_RGBA(0, 0, 0, 0));
+ }
+
+ virtual bool ClearsBackground() override
+ {
+ return true;
+ }
+
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override
+ {
+ return mozilla::LAYER_ACTIVE_FORCE;
+ }
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ NS_DISPLAY_DECL_NAME("ClearBackground", TYPE_CLEAR_BACKGROUND)
+};
+
+/**
+ * The standard display item to paint the outer CSS box-shadows of a frame.
+ */
+class nsDisplayBoxShadowOuter final : public nsDisplayItem {
+public:
+ nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ , mOpacity(1.0) {
+ MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter);
+ mBounds = GetBoundsInternal();
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayBoxShadowOuter() {
+ MOZ_COUNT_DTOR(nsDisplayBoxShadowOuter);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual bool IsInvisibleInRect(const nsRect& aRect) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER)
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip) override
+ {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ mOpacity = aOpacity;
+ if (aClip) {
+ IntersectClip(aBuilder, *aClip);
+ }
+ }
+ virtual bool CanApplyOpacity() const override
+ {
+ return true;
+ }
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayBoxShadowOuterGeometry(this, aBuilder, mOpacity);
+ }
+
+ nsRect GetBoundsInternal();
+
+private:
+ nsRegion mVisibleRegion;
+ nsRect mBounds;
+ float mOpacity;
+};
+
+/**
+ * The standard display item to paint the inner CSS box-shadows of a frame.
+ */
+class nsDisplayBoxShadowInner : public nsDisplayItem {
+public:
+ nsDisplayBoxShadowInner(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayBoxShadowInner);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayBoxShadowInner() {
+ MOZ_COUNT_DTOR(nsDisplayBoxShadowInner);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ NS_DISPLAY_DECL_NAME("BoxShadowInner", TYPE_BOX_SHADOW_INNER)
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayBoxShadowInnerGeometry(this, aBuilder);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ const nsDisplayBoxShadowInnerGeometry* geometry = static_cast<const nsDisplayBoxShadowInnerGeometry*>(aGeometry);
+ if (!geometry->mPaddingRect.IsEqualInterior(GetPaddingRect())) {
+ // nsDisplayBoxShadowInner is based around the padding rect, but it can
+ // touch pixels outside of this. We should invalidate the entire bounds.
+ bool snap;
+ aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap));
+ }
+ }
+
+private:
+ nsRegion mVisibleRegion;
+};
+
+/**
+ * The standard display item to paint the CSS outline of a frame.
+ */
+class nsDisplayOutline : public nsDisplayItem {
+public:
+ nsDisplayOutline(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayOutline);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayOutline() {
+ MOZ_COUNT_DTOR(nsDisplayOutline);
+ }
+#endif
+
+ virtual bool IsInvisibleInRect(const nsRect& aRect) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("Outline", TYPE_OUTLINE)
+};
+
+/**
+ * A class that lets you receive events within the frame bounds but never paints.
+ */
+class nsDisplayEventReceiver : public nsDisplayItem {
+public:
+ nsDisplayEventReceiver(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayEventReceiver);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayEventReceiver() {
+ MOZ_COUNT_DTOR(nsDisplayEventReceiver);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ NS_DISPLAY_DECL_NAME("EventReceiver", TYPE_EVENT_RECEIVER)
+};
+
+/**
+ * A display item that tracks event-sensitive regions which will be set
+ * on the ContainerLayer that eventually contains this item.
+ *
+ * One of these is created for each stacking context and pseudo-stacking-context.
+ * It accumulates regions for event targets contributed by the border-boxes of
+ * frames in its (pseudo) stacking context. A nsDisplayLayerEventRegions
+ * eventually contributes its regions to the PaintedLayer it is placed in by
+ * FrameLayerBuilder. (We don't create a display item for every frame that
+ * could be an event target (i.e. almost all frames), because that would be
+ * high overhead.)
+ *
+ * We always make leaf layers other than PaintedLayers transparent to events.
+ * For example, an event targeting a canvas or video will actually target the
+ * background of that element, which is logically in the PaintedLayer behind the
+ * CanvasFrame or ImageFrame. We only need to create a
+ * nsDisplayLayerEventRegions when an element's background could be in front
+ * of a lower z-order element with its own layer.
+ */
+class nsDisplayLayerEventRegions final : public nsDisplayItem {
+public:
+ nsDisplayLayerEventRegions(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ {
+ MOZ_COUNT_CTOR(nsDisplayLayerEventRegions);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayLayerEventRegions() {
+ MOZ_COUNT_DTOR(nsDisplayLayerEventRegions);
+ }
+#endif
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ *aSnap = false;
+ return nsRect();
+ }
+ nsRect GetHitRegionBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+ {
+ *aSnap = false;
+ return mHitRegion.GetBounds().Union(mMaybeHitRegion.GetBounds());
+ }
+
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip) override
+ {
+ NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+ }
+ virtual bool CanApplyOpacity() const override
+ {
+ return true;
+ }
+
+ NS_DISPLAY_DECL_NAME("LayerEventRegions", TYPE_LAYER_EVENT_REGIONS)
+
+ // Indicate that aFrame's border-box contributes to the event regions for
+ // this layer. aFrame must have the same reference frame as mFrame.
+ void AddFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+
+ // Indicate that an inactive scrollframe's scrollport should be added to the
+ // dispatch-to-content region, to ensure that APZ lets content create a
+ // displayport.
+ void AddInactiveScrollPort(const nsRect& aRect);
+
+ bool IsEmpty() const;
+
+ int32_t ZIndex() const override;
+ void SetOverrideZIndex(int32_t aZIndex);
+
+ const nsRegion& HitRegion() { return mHitRegion; }
+ const nsRegion& MaybeHitRegion() { return mMaybeHitRegion; }
+ const nsRegion& DispatchToContentHitRegion() { return mDispatchToContentHitRegion; }
+ const nsRegion& NoActionRegion() { return mNoActionRegion; }
+ const nsRegion& HorizontalPanRegion() { return mHorizontalPanRegion; }
+ const nsRegion& VerticalPanRegion() { return mVerticalPanRegion; }
+ nsRegion CombinedTouchActionRegion();
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+private:
+ // Relative to aFrame's reference frame.
+ // These are the points that are definitely in the hit region.
+ nsRegion mHitRegion;
+ // These are points that may or may not be in the hit region. Only main-thread
+ // event handling can tell for sure (e.g. because complex shapes are present).
+ nsRegion mMaybeHitRegion;
+ // These are points that need to be dispatched to the content thread for
+ // resolution. Always contained in the union of mHitRegion and mMaybeHitRegion.
+ nsRegion mDispatchToContentHitRegion;
+ // These are points where panning is disabled, as determined by the touch-action
+ // property. Always contained in the union of mHitRegion and mMaybeHitRegion.
+ nsRegion mNoActionRegion;
+ // These are points where panning is horizontal, as determined by the touch-action
+ // property. Always contained in the union of mHitRegion and mMaybeHitRegion.
+ nsRegion mHorizontalPanRegion;
+ // These are points where panning is vertical, as determined by the touch-action
+ // property. Always contained in the union of mHitRegion and mMaybeHitRegion.
+ nsRegion mVerticalPanRegion;
+ // If these event regions are for an inactive scroll frame, the z-index of
+ // this display item is overridden to be the largest z-index of the content
+ // in the scroll frame. This ensures that the event regions item remains on
+ // top of the content after sorting items by z-index.
+ mozilla::Maybe<int32_t> mOverrideZIndex;
+};
+
+/**
+ * A class that lets you wrap a display list as a display item.
+ *
+ * GetUnderlyingFrame() is troublesome for wrapped lists because if the wrapped
+ * list has many items, it's not clear which one has the 'underlying frame'.
+ * Thus we force the creator to specify what the underlying frame is. The
+ * underlying frame should be the root of a stacking context, because sorting
+ * a list containing this item will not get at the children.
+ *
+ * In some cases (e.g., clipping) we want to wrap a list but we don't have a
+ * particular underlying frame that is a stacking context root. In that case
+ * we allow the frame to be nullptr. Callers to GetUnderlyingFrame must
+ * detect and handle this case.
+ */
+class nsDisplayWrapList : public nsDisplayItem {
+public:
+ /**
+ * Takes all the items from aList and puts them in our list.
+ */
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip);
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayItem* aItem);
+ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame), mOverrideZIndex(0), mHasZIndexOverride(false)
+ {
+ MOZ_COUNT_CTOR(nsDisplayWrapList);
+ mBaseVisibleRect = mVisibleRect;
+ }
+ virtual ~nsDisplayWrapList();
+ /**
+ * Call this if the wrapped list is changed.
+ */
+ virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ mBounds = mList.GetScrollClippedBoundsUpTo(aBuilder, mScrollClip);
+ // The display list may contain content that's visible outside the visible
+ // rect (i.e. the current dirty rect) passed in when the item was created.
+ // This happens when the dirty rect has been restricted to the visual
+ // overflow rect of a frame for some reason (e.g. when setting up dirty
+ // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that
+ // frame contains placeholders for out-of-flows that aren't descendants of
+ // the frame.
+ mVisibleRect.UnionRect(mBaseVisibleRect, mList.GetVisibleRect());
+ }
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual bool TryMerge(nsDisplayItem* aItem) override {
+ return false;
+ }
+ virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) override
+ {
+ aFrames->AppendElements(mMergedFrames);
+ }
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return true;
+ }
+ virtual bool IsInvalid(nsRect& aRect) override
+ {
+ if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
+ return true;
+ }
+ nsRect temp;
+ for (uint32_t i = 0; i < mMergedFrames.Length(); i++) {
+ if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) {
+ aRect.SetEmpty();
+ return true;
+ }
+ aRect = aRect.Union(temp);
+ }
+ aRect += ToReferenceFrame();
+ return !aRect.IsEmpty();
+ }
+ NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override;
+
+ virtual nsDisplayList* GetSameCoordinateSystemChildren() override
+ {
+ NS_ASSERTION(mList.IsEmpty() || !ReferenceFrame() ||
+ !mList.GetBottom()->ReferenceFrame() ||
+ mList.GetBottom()->ReferenceFrame() == ReferenceFrame(),
+ "Children must have same reference frame");
+ return &mList;
+ }
+ virtual nsDisplayList* GetChildren() override { return &mList; }
+
+ virtual int32_t ZIndex() const override
+ {
+ return (mHasZIndexOverride) ? mOverrideZIndex : nsDisplayItem::ZIndex();
+ }
+
+ void SetOverrideZIndex(int32_t aZIndex)
+ {
+ mHasZIndexOverride = true;
+ mOverrideZIndex = aZIndex;
+ }
+
+ void SetVisibleRect(const nsRect& aRect);
+
+ void SetReferenceFrame(const nsIFrame* aFrame);
+
+ /**
+ * This creates a copy of this item, but wrapping aItem instead of
+ * our existing list. Only gets called if this item returned nullptr
+ * for GetUnderlyingFrame(). aItem is guaranteed to return non-null from
+ * GetUnderlyingFrame().
+ */
+ virtual nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ NS_NOTREACHED("We never returned nullptr for GetUnderlyingFrame!");
+ return nullptr;
+ }
+
+protected:
+ nsDisplayWrapList() {}
+
+ void MergeFromTrackingMergedFrames(nsDisplayWrapList* aOther)
+ {
+ mList.AppendToBottom(&aOther->mList);
+ mBounds.UnionRect(mBounds, aOther->mBounds);
+ mVisibleRect.UnionRect(mVisibleRect, aOther->mVisibleRect);
+ mMergedFrames.AppendElement(aOther->mFrame);
+ mMergedFrames.AppendElements(mozilla::Move(aOther->mMergedFrames));
+ }
+
+ nsDisplayList mList;
+ // The frames from items that have been merged into this item, excluding
+ // this item's own frame.
+ nsTArray<nsIFrame*> mMergedFrames;
+ nsRect mBounds;
+ // Visible rect contributed by this display item itself.
+ // Our mVisibleRect may include the visible areas of children.
+ nsRect mBaseVisibleRect;
+ int32_t mOverrideZIndex;
+ bool mHasZIndexOverride;
+};
+
+/**
+ * We call WrapDisplayList on the in-flow lists: BorderBackground(),
+ * BlockBorderBackgrounds() and Content().
+ * We call WrapDisplayItem on each item of Outlines(), PositionedDescendants(),
+ * and Floats(). This is done to support special wrapping processing for frames
+ * that may not be in-flow descendants of the current frame.
+ */
+class nsDisplayWrapper {
+public:
+ // This is never instantiated directly (it has pure virtual methods), so no
+ // need to count constructors and destructors.
+
+ virtual bool WrapBorderBackground() { return true; }
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList) = 0;
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) = 0;
+
+ nsresult WrapLists(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsDisplayListSet& aIn, const nsDisplayListSet& aOut);
+ nsresult WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsDisplayListSet& aLists);
+protected:
+ nsDisplayWrapper() {}
+};
+
+/**
+ * The standard display item to paint a stacking context with translucency
+ * set by the stacking context root frame's 'opacity' style.
+ */
+class nsDisplayOpacity : public nsDisplayWrapList {
+public:
+ nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip,
+ bool aForEventsAndPluginsOnly);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayOpacity();
+#endif
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ // We don't need to compute an invalidation region since we have LayerTreeInvalidation
+ }
+ virtual bool IsInvalid(nsRect& aRect) override
+ {
+ if (mForEventsAndPluginsOnly) {
+ return false;
+ }
+ return nsDisplayWrapList::IsInvalid(aRect);
+ }
+ virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
+ float aOpacity,
+ const DisplayItemClip* aClip) override;
+ virtual bool CanApplyOpacity() const override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
+ static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+ NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY)
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+
+private:
+ float mOpacity;
+ bool mForEventsAndPluginsOnly;
+};
+
+class nsDisplayBlendMode : public nsDisplayWrapList {
+public:
+ nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, uint8_t aBlendMode,
+ const DisplayItemScrollClip* aScrollClip,
+ uint32_t aIndex = 0);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayBlendMode();
+#endif
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ // We don't need to compute an invalidation region since we have LayerTreeInvalidation
+ }
+ virtual uint32_t GetPerFrameKey() override {
+ return (mIndex << nsDisplayItem::TYPE_BITS) |
+ nsDisplayItem::GetPerFrameKey();
+ }
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE)
+
+private:
+ uint8_t mBlendMode;
+ uint32_t mIndex;
+};
+
+class nsDisplayBlendContainer : public nsDisplayWrapList {
+public:
+ static nsDisplayBlendContainer*
+ CreateForMixBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const DisplayItemScrollClip* aScrollClip);
+
+ static nsDisplayBlendContainer*
+ CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, const DisplayItemScrollClip* aScrollClip);
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayBlendContainer();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ virtual uint32_t GetPerFrameKey() override {
+ return (mIsForBackground ? 1 << nsDisplayItem::TYPE_BITS : 0) |
+ nsDisplayItem::GetPerFrameKey();
+ }
+ NS_DISPLAY_DECL_NAME("BlendContainer", TYPE_BLEND_CONTAINER)
+
+private:
+ nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip,
+ bool aIsForBackground);
+
+ // Used to distinguish containers created at building stacking
+ // context or appending background.
+ bool mIsForBackground;
+};
+
+/**
+ * A display item that has no purpose but to ensure its contents get
+ * their own layer.
+ */
+class nsDisplayOwnLayer : public nsDisplayWrapList {
+public:
+
+ /**
+ * nsDisplayOwnLayer constructor flags
+ */
+ enum {
+ GENERATE_SUBDOC_INVALIDATIONS = 0x01,
+ VERTICAL_SCROLLBAR = 0x02,
+ HORIZONTAL_SCROLLBAR = 0x04,
+ GENERATE_SCROLLABLE_LAYER = 0x08,
+ SCROLLBAR_CONTAINER = 0x10
+ };
+
+ /**
+ * @param aFlags GENERATE_SUBDOC_INVALIDATIONS :
+ * Add UserData to the created ContainerLayer, so that invalidations
+ * for this layer are send to our nsPresContext.
+ * GENERATE_SCROLLABLE_LAYER : only valid on nsDisplaySubDocument (and
+ * subclasses), indicates this layer is to be a scrollable layer, so call
+ * ComputeFrameMetrics, etc.
+ * @param aScrollTarget when VERTICAL_SCROLLBAR or HORIZONTAL_SCROLLBAR
+ * is set in the flags, this parameter should be the ViewID of the
+ * scrollable content this scrollbar is for.
+ */
+ nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, uint32_t aFlags = 0,
+ ViewID aScrollTarget = mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
+ float aScrollbarThumbRatio = 0.0f,
+ bool aForceActive = true);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayOwnLayer();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual bool TryMerge(nsDisplayItem* aItem) override
+ {
+ // Don't allow merging, each sublist must have its own layer
+ return false;
+ }
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ uint32_t GetFlags() { return mFlags; }
+ NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER)
+protected:
+ uint32_t mFlags;
+ ViewID mScrollTarget;
+ float mScrollbarThumbRatio;
+ bool mForceActive;
+};
+
+/**
+ * A display item for subdocuments. This is more or less the same as nsDisplayOwnLayer,
+ * except that it always populates the FrameMetrics instance on the ContainerLayer it
+ * builds.
+ */
+class nsDisplaySubDocument : public nsDisplayOwnLayer {
+public:
+ nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, uint32_t aFlags);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySubDocument();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+
+ NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT)
+
+ mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
+ const ContainerLayerParameters& aContainerParameters);
+
+protected:
+ ViewID mScrollParentId;
+ bool mForceDispatchToContentRegion;
+};
+
+/**
+ * A display item for subdocuments to capture the resolution from the presShell
+ * and ensure that it gets applied to all the right elements. This item creates
+ * a container layer.
+ */
+class nsDisplayResolution : public nsDisplaySubDocument {
+public:
+ nsDisplayResolution(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, uint32_t aFlags);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayResolution();
+#endif
+ virtual void HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ NS_DISPLAY_DECL_NAME("Resolution", TYPE_RESOLUTION)
+};
+
+/**
+ * A display item used to represent sticky position elements. The contents
+ * gets its own layer and creates a stacking context, and the layer will have
+ * position-related metadata set on it.
+ */
+class nsDisplayStickyPosition : public nsDisplayOwnLayer {
+public:
+ nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayStickyPosition();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION)
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override
+ {
+ return mozilla::LAYER_ACTIVE;
+ }
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+};
+
+class nsDisplayFixedPosition : public nsDisplayOwnLayer {
+public:
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
+
+ static nsDisplayFixedPosition* CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayBackgroundImage* aImage,
+ uint32_t aIndex);
+
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayFixedPosition();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION)
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override
+ {
+ return mozilla::LAYER_ACTIVE;
+ }
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+
+ virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override { return mIsFixedBackground; }
+
+ virtual uint32_t GetPerFrameKey() override { return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+
+ AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override {
+ return mAnimatedGeometryRootForScrollMetadata;
+ }
+
+private:
+ // For background-attachment:fixed
+ nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, uint32_t aIndex);
+ void Init(nsDisplayListBuilder* aBuilder);
+
+ AnimatedGeometryRoot* mAnimatedGeometryRootForScrollMetadata;
+ uint32_t mIndex;
+ bool mIsFixedBackground;
+};
+
+/**
+ * This creates an empty scrollable layer. It has no child layers.
+ * It is used to record the existence of a scrollable frame in the layer
+ * tree.
+ */
+class nsDisplayScrollInfoLayer : public nsDisplayWrapList
+{
+public:
+ nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame);
+ NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER)
+
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayScrollInfoLayer();
+#endif
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override
+ { return true; }
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override {
+ *aSnap = false;
+ return nsRegion();
+ }
+
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
+ { return false; }
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+ mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
+ const ContainerLayerParameters& aContainerParameters);
+
+protected:
+ nsIFrame* mScrollFrame;
+ nsIFrame* mScrolledFrame;
+ ViewID mScrollParentId;
+};
+
+/**
+ * nsDisplayZoom is used for subdocuments that have a different full zoom than
+ * their parent documents. This item creates a container layer.
+ */
+class nsDisplayZoom : public nsDisplaySubDocument {
+public:
+ /**
+ * @param aFrame is the root frame of the subdocument.
+ * @param aList contains the display items for the subdocument.
+ * @param aAPD is the app units per dev pixel ratio of the subdocument.
+ * @param aParentAPD is the app units per dev pixel ratio of the parent
+ * document.
+ * @param aFlags GENERATE_SUBDOC_INVALIDATIONS :
+ * Add UserData to the created ContainerLayer, so that invalidations
+ * for this layer are send to our nsPresContext.
+ */
+ nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList,
+ int32_t aAPD, int32_t aParentAPD,
+ uint32_t aFlags = 0);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayZoom();
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override
+ {
+ return mozilla::LAYER_ACTIVE;
+ }
+ NS_DISPLAY_DECL_NAME("Zoom", TYPE_ZOOM)
+
+ // Get the app units per dev pixel ratio of the child document.
+ int32_t GetChildAppUnitsPerDevPixel() { return mAPD; }
+ // Get the app units per dev pixel ratio of the parent document.
+ int32_t GetParentAppUnitsPerDevPixel() { return mParentAPD; }
+
+private:
+ int32_t mAPD, mParentAPD;
+};
+
+class nsDisplaySVGEffects: public nsDisplayWrapList {
+public:
+ nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, bool aHandleOpacity,
+ const DisplayItemScrollClip* aScrollClip);
+ nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, bool aHandleOpacity);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySVGEffects();
+#endif
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+
+ gfxRect BBoxInUserSpace() const;
+ gfxPoint UserSpaceOffset() const;
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+protected:
+ bool ValidateSVGFrame();
+
+ // relative to mFrame
+ nsRect mEffectsBounds;
+ // True if we need to handle css opacity in this display item.
+ bool mHandleOpacity;
+};
+
+/**
+ * A display item to paint a stacking context with mask and clip effects
+ * set by the stacking context root frame's style.
+ */
+class nsDisplayMask : public nsDisplaySVGEffects {
+public:
+ typedef mozilla::layers::ImageLayer ImageLayer;
+ typedef class mozilla::gfx::DrawTarget DrawTarget;
+
+ nsDisplayMask(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, bool aHandleOpacity,
+ const DisplayItemScrollClip* aScrollClip);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMask();
+#endif
+
+ NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK)
+
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayMaskGeometry(this, aBuilder);
+ }
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+#ifdef MOZ_DUMP_PAINTING
+ void PrintEffects(nsACString& aTo);
+#endif
+
+ void PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ LayerManager* aManager);
+
+ /*
+ * Paint mask onto aMaskContext in mFrame's coordinate space.
+ */
+ bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext);
+
+ const nsTArray<nsRect>& GetDestRects()
+ {
+ return mDestRects;
+ }
+private:
+ // According to mask property and the capability of aManager, determine
+ // whether paint mask onto a dedicate mask layer.
+ bool ShouldPaintOnMaskLayer(LayerManager* aManager);
+
+ nsTArray<nsRect> mDestRects;
+};
+
+/**
+ * A display item to paint a stacking context with filter effects set by the
+ * stacking context root frame's style.
+ */
+class nsDisplayFilter : public nsDisplaySVGEffects {
+public:
+ nsDisplayFilter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, bool aHandleOpacity);
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayFilter();
+#endif
+
+ NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER)
+
+ virtual bool TryMerge(nsDisplayItem* aItem) override;
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override {
+ *aSnap = false;
+ return mEffectsBounds + ToReferenceFrame();
+ }
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override;
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayFilterGeometry(this, aBuilder);
+ }
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+#ifdef MOZ_DUMP_PAINTING
+ void PrintEffects(nsACString& aTo);
+#endif
+
+ void PaintAsLayer(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx,
+ LayerManager* aManager);
+};
+
+/* A display item that applies a transformation to all of its descendant
+ * elements. This wrapper should only be used if there is a transform applied
+ * to the root element.
+ *
+ * The reason that a "bounds" rect is involved in transform calculations is
+ * because CSS-transforms allow percentage values for the x and y components
+ * of <translation-value>s, where percentages are percentages of the element's
+ * border box.
+ *
+ * INVARIANT: The wrapped frame is transformed or we supplied a transform getter
+ * function.
+ * INVARIANT: The wrapped frame is non-null.
+ */
+class nsDisplayTransform: public nsDisplayItem
+{
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::gfx::Point3D Point3D;
+
+ /*
+ * Avoid doing UpdateBounds() during construction.
+ */
+ class StoreList : public nsDisplayWrapList {
+ public:
+ StoreList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList) :
+ nsDisplayWrapList(aBuilder, aFrame, aList) {}
+ StoreList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayItem* aItem) :
+ nsDisplayWrapList(aBuilder, aFrame, aItem) {}
+ virtual ~StoreList() {}
+
+ virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) override {
+ // For extending 3d rendering context, the bounds would be
+ // updated by DoUpdateBoundsPreserves3D(), not here.
+ if (!mFrame->Extend3DContext()) {
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+ }
+ }
+ virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
+ for (nsDisplayItem *i = mList.GetBottom(); i; i = i->GetAbove()) {
+ i->DoUpdateBoundsPreserves3D(aBuilder);
+ }
+ nsDisplayWrapList::UpdateBounds(aBuilder);
+ }
+ };
+
+public:
+ /**
+ * Returns a matrix (in pixels) for the current frame. The matrix should be relative to
+ * the current frame's coordinate space.
+ *
+ * @param aFrame The frame to compute the transform for.
+ * @param aAppUnitsPerPixel The number of app units per graphics unit.
+ */
+ typedef Matrix4x4 (* ComputeTransformFunction)(nsIFrame* aFrame, float aAppUnitsPerPixel);
+
+ /* Constructor accepts a display list, empties it, and wraps it up. It also
+ * ferries the underlying frame to the nsDisplayItem constructor.
+ */
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
+ nsDisplayList *aList, const nsRect& aChildrenVisibleRect,
+ uint32_t aIndex = 0, bool aIsFullyVisible = false);
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
+ nsDisplayItem *aItem, const nsRect& aChildrenVisibleRect,
+ uint32_t aIndex = 0);
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
+ nsDisplayList *aList, const nsRect& aChildrenVisibleRect,
+ ComputeTransformFunction aTransformGetter, uint32_t aIndex = 0);
+ nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
+ nsDisplayList *aList, const nsRect& aChildrenVisibleRect,
+ const Matrix4x4& aTransform, uint32_t aIndex = 0);
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTransform()
+ {
+ MOZ_COUNT_DTOR(nsDisplayTransform);
+ }
+#endif
+
+ NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ if (mStoredList.GetComponentAlphaBounds(aBuilder).IsEmpty())
+ return nsRect();
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ virtual nsDisplayList* GetChildren() override { return mStoredList.GetChildren(); }
+
+ virtual void HitTest(nsDisplayListBuilder *aBuilder, const nsRect& aRect,
+ HitTestState *aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder *aBuilder, bool* aSnap) override;
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
+ bool* aSnap) override;
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder *aBuilder) override;
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+ virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
+ virtual bool ComputeVisibility(nsDisplayListBuilder *aBuilder,
+ nsRegion *aVisibleRegion) override;
+ virtual bool TryMerge(nsDisplayItem *aItem) override;
+
+ virtual uint32_t GetPerFrameKey() override { return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ // We don't need to compute an invalidation region since we have LayerTreeInvalidation
+ }
+
+ virtual const nsIFrame* ReferenceFrameForChildren() const override {
+ // If we were created using a transform-getter, then we don't
+ // belong to a transformed frame, and aren't a reference frame
+ // for our children.
+ if (!mTransformGetter) {
+ return mFrame;
+ }
+ return nsDisplayItem::ReferenceFrameForChildren();
+ }
+
+ AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override {
+ return mAnimatedGeometryRootForScrollMetadata;
+ }
+
+ virtual const nsRect& GetVisibleRectForChildren() const override
+ {
+ return mChildrenVisibleRect;
+ }
+
+ enum {
+ INDEX_MAX = UINT32_MAX >> nsDisplayItem::TYPE_BITS
+ };
+
+ /**
+ * We include the perspective matrix from our containing block for the
+ * purposes of visibility calculations, but we exclude it from the transform
+ * we set on the layer (for rendering), since there will be an
+ * nsDisplayPerspective created for that.
+ */
+ const Matrix4x4& GetTransform();
+ Matrix4x4 GetTransformForRendering();
+
+ /**
+ * Return the transform that is aggregation of all transform on the
+ * preserves3d chain.
+ */
+ const Matrix4x4& GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder);
+
+ float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint);
+
+ /**
+ * TransformRect takes in as parameters a rectangle (in aFrame's coordinate
+ * space) and returns the smallest rectangle (in aFrame's coordinate space)
+ * containing the transformed image of that rectangle. That is, it takes
+ * the four corners of the rectangle, transforms them according to the
+ * matrix associated with the specified frame, then returns the smallest
+ * rectangle containing the four transformed points.
+ *
+ * @param untransformedBounds The rectangle (in app units) to transform.
+ * @param aFrame The frame whose transformation should be applied. This
+ * function raises an assertion if aFrame is null or doesn't have a
+ * transform applied to it.
+ * @param aOrigin The origin of the transform relative to aFrame's local
+ * coordinate space.
+ * @param aBoundsOverride (optional) Rather than using the frame's computed
+ * bounding rect as frame bounds, use this rectangle instead. Pass
+ * nullptr (or nothing at all) to use the default.
+ */
+ static nsRect TransformRect(const nsRect &aUntransformedBounds,
+ const nsIFrame* aFrame,
+ const nsRect* aBoundsOverride = nullptr);
+
+ /* UntransformRect is like TransformRect, except that it inverts the
+ * transform.
+ */
+ static bool UntransformRect(const nsRect &aTransformedBounds,
+ const nsRect &aChildBounds,
+ const nsIFrame* aFrame,
+ nsRect *aOutRect);
+
+ bool UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
+ nsRect* aOutRect);
+
+ static Point3D GetDeltaToTransformOrigin(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ const nsRect* aBoundsOverride);
+
+ /*
+ * Returns true if aFrame has perspective applied from its containing
+ * block.
+ * Returns the matrix to append to apply the persective (taking
+ * perspective-origin into account), relative to aFrames coordinate
+ * space).
+ * aOutMatrix is assumed to be the identity matrix, and isn't explicitly
+ * cleared.
+ */
+ static bool ComputePerspectiveMatrix(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ Matrix4x4& aOutMatrix);
+
+ struct FrameTransformProperties
+ {
+ FrameTransformProperties(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel,
+ const nsRect* aBoundsOverride);
+ FrameTransformProperties(nsCSSValueSharedList* aTransformList,
+ const Point3D& aToTransformOrigin)
+ : mFrame(nullptr)
+ , mTransformList(aTransformList)
+ , mToTransformOrigin(aToTransformOrigin)
+ {}
+
+ const nsIFrame* mFrame;
+ RefPtr<nsCSSValueSharedList> mTransformList;
+ const Point3D mToTransformOrigin;
+ };
+
+ /**
+ * Given a frame with the -moz-transform property or an SVG transform,
+ * returns the transformation matrix for that frame.
+ *
+ * @param aFrame The frame to get the matrix from.
+ * @param aOrigin Relative to which point this transform should be applied.
+ * @param aAppUnitsPerPixel The number of app units per graphics unit.
+ * @param aBoundsOverride [optional] If this is nullptr (the default), the
+ * computation will use the value of TransformReferenceBox(aFrame).
+ * Otherwise, it will use the value of aBoundsOverride. This is
+ * mostly for internal use and in most cases you will not need to
+ * specify a value.
+ * @param aFlags OFFSET_BY_ORIGIN The resulting matrix will be translated
+ * by aOrigin. This translation is applied *before* the CSS transform.
+ * @param aFlags INCLUDE_PRESERVE3D_ANCESTORS The computed transform will
+ * include the transform of any ancestors participating in the same
+ * 3d rendering context.
+ * @param aFlags INCLUDE_PERSPECTIVE The resulting matrix will include the
+ * perspective transform from the containing block if applicable.
+ */
+ enum {
+ OFFSET_BY_ORIGIN = 1 << 0,
+ INCLUDE_PRESERVE3D_ANCESTORS = 1 << 1,
+ INCLUDE_PERSPECTIVE = 1 << 2,
+ };
+ static Matrix4x4 GetResultingTransformMatrix(const nsIFrame* aFrame,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride = nullptr);
+ static Matrix4x4 GetResultingTransformMatrix(const FrameTransformProperties& aProperties,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride = nullptr);
+ /**
+ * Return true when we should try to prerender the entire contents of the
+ * transformed frame even when it's not completely visible (yet).
+ */
+ static bool ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+ bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
+
+ bool MayBeAnimated(nsDisplayListBuilder* aBuilder);
+
+ virtual void WriteDebugInfo(std::stringstream& aStream) override;
+
+ // Force the layer created for this item not to extend 3D context.
+ // See nsIFrame::BuildDisplayListForStackingContext()
+ void SetNoExtendContext() { mNoExtendContext = true; }
+
+ virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
+ MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() ||
+ IsTransformSeparator());
+ // Updating is not going through to child 3D context.
+ ComputeBounds(aBuilder);
+ }
+
+ /**
+ * This function updates bounds for items with a frame establishing
+ * 3D rendering context.
+ *
+ * \see nsDisplayItem::DoUpdateBoundsPreserves3D()
+ */
+ void UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) {
+ if (!mFrame->Extend3DContext() ||
+ mFrame->Combines3DTransformWithAncestors() ||
+ IsTransformSeparator()) {
+ // Not an establisher of a 3D rendering context.
+ return;
+ }
+ // Always start updating from an establisher of a 3D rendering context.
+
+ nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder);
+ nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
+ accTransform.StartRoot();
+ ComputeBounds(aBuilder);
+ mBounds = aBuilder->GetAccumulatedRect();
+ mHasBounds = true;
+ }
+
+ /**
+ * This item is an additional item as the boundary between parent
+ * and child 3D rendering context.
+ * \see nsIFrame::BuildDisplayListForStackingContext().
+ */
+ bool IsTransformSeparator() { return mIsTransformSeparator; }
+ /**
+ * This item is the boundary between parent and child 3D rendering
+ * context.
+ */
+ bool IsLeafOf3DContext() {
+ return (IsTransformSeparator() ||
+ (!mFrame->Extend3DContext() &&
+ mFrame->Combines3DTransformWithAncestors()));
+ }
+ /**
+ * The backing frame of this item participates a 3D rendering
+ * context.
+ */
+ bool IsParticipating3DContext() {
+ return mFrame->Extend3DContext() ||
+ mFrame->Combines3DTransformWithAncestors();
+ }
+
+private:
+ void ComputeBounds(nsDisplayListBuilder* aBuilder);
+ void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder);
+ void Init(nsDisplayListBuilder* aBuilder);
+
+ static Matrix4x4 GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties,
+ const nsPoint& aOrigin,
+ float aAppUnitsPerPixel,
+ uint32_t aFlags,
+ const nsRect* aBoundsOverride);
+
+ StoreList mStoredList;
+ Matrix4x4 mTransform;
+ // Accumulated transform of ancestors on the preserves-3d chain.
+ Matrix4x4 mTransformPreserves3D;
+ ComputeTransformFunction mTransformGetter;
+ AnimatedGeometryRoot* mAnimatedGeometryRootForChildren;
+ AnimatedGeometryRoot* mAnimatedGeometryRootForScrollMetadata;
+ nsRect mChildrenVisibleRect;
+ uint32_t mIndex;
+ nsRect mBounds;
+ // True for mBounds is valid.
+ bool mHasBounds;
+ // Be forced not to extend 3D context. Since we don't create a
+ // transform item, a container layer, for every frames in a
+ // preserves3d context, the transform items of a child preserves3d
+ // context may extend the parent context not intented if the root of
+ // the child preserves3d context doesn't create a transform item.
+ // With this flags, we force the item not extending 3D context.
+ bool mNoExtendContext;
+ // This item is a separator between 3D rendering contexts, and
+ // mTransform have been presetted by the constructor.
+ bool mIsTransformSeparator;
+ // True if mTransformPreserves3D have been initialized.
+ bool mTransformPreserves3DInited;
+ // True if the entire untransformed area has been treated as
+ // visible during display list construction.
+ bool mIsFullyVisible;
+};
+
+/* A display item that applies a perspective transformation to a single
+ * nsDisplayTransform child item. We keep this as a separate item since the
+ * perspective-origin is relative to an ancestor of the transformed frame, and
+ * APZ can scroll the child separately.
+ */
+class nsDisplayPerspective : public nsDisplayItem
+{
+ typedef mozilla::gfx::Point3D Point3D;
+
+public:
+ NS_DISPLAY_DECL_NAME("nsDisplayPerspective", TYPE_PERSPECTIVE)
+
+ nsDisplayPerspective(nsDisplayListBuilder* aBuilder, nsIFrame* aTransformFrame,
+ nsIFrame* aPerspectiveFrame,
+ nsDisplayList* aList);
+
+ virtual uint32_t GetPerFrameKey() override { return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override
+ {
+ return mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+ }
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ return mList.GetBounds(aBuilder, aSnap);
+ }
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {}
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override
+ {
+ return mList.GetOpaqueRegion(aBuilder, aSnap);
+ }
+
+ virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+ {
+ return mList.IsUniform(aBuilder);
+ }
+
+ virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aParameters) override;
+
+ virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override
+ {
+ if (!mList.GetChildren()->GetTop()) {
+ return false;
+ }
+ return mList.GetChildren()->GetTop()->ShouldBuildLayerEvenIfInvisible(aBuilder);
+ }
+
+ virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const ContainerLayerParameters& aContainerParameters) override;
+
+ virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
+ nsRegion* aVisibleRegion) override
+ {
+ mList.RecomputeVisibility(aBuilder, aVisibleRegion);
+ return true;
+ }
+ virtual nsDisplayList* GetSameCoordinateSystemChildren() override { return mList.GetChildren(); }
+ virtual nsDisplayList* GetChildren() override { return mList.GetChildren(); }
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ return mList.GetComponentAlphaBounds(aBuilder);
+ }
+
+ nsIFrame* TransformFrame() { return mTransformFrame; }
+
+ virtual int32_t ZIndex() const override;
+
+ virtual void
+ DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
+ if (mList.GetChildren()->GetTop()) {
+ static_cast<nsDisplayTransform*>(mList.GetChildren()->GetTop())->DoUpdateBoundsPreserves3D(aBuilder);
+ }
+ }
+
+private:
+ nsDisplayWrapList mList;
+ nsIFrame* mTransformFrame;
+ uint32_t mIndex;
+};
+
+/**
+ * This class adds basic support for limiting the rendering (in the inline axis
+ * of the writing mode) to the part inside the specified edges. It's a base
+ * class for the display item classes that do the actual work.
+ * The two members, mVisIStartEdge and mVisIEndEdge, are relative to the edges
+ * of the frame's scrollable overflow rectangle and are the amount to suppress
+ * on each side.
+ *
+ * Setting none, both or only one edge is allowed.
+ * The values must be non-negative.
+ * The default value for both edges is zero, which means everything is painted.
+ */
+class nsCharClipDisplayItem : public nsDisplayItem {
+public:
+ nsCharClipDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame), mVisIStartEdge(0), mVisIEndEdge(0) {}
+
+ explicit nsCharClipDisplayItem(nsIFrame* aFrame)
+ : nsDisplayItem(aFrame) {}
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+ struct ClipEdges {
+ ClipEdges(const nsDisplayItem& aItem,
+ nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
+ nsRect r = aItem.Frame()->GetScrollableOverflowRect() +
+ aItem.ToReferenceFrame();
+ if (aItem.Frame()->GetWritingMode().IsVertical()) {
+ mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd =
+ aVisIEndEdge > 0 ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ } else {
+ mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd =
+ aVisIEndEdge > 0 ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ }
+ }
+ void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
+ nscoord end = *aVisIStart + *aVisISize;
+ *aVisIStart = std::max(*aVisIStart, mVisIStart);
+ *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
+ }
+ nscoord mVisIStart;
+ nscoord mVisIEnd;
+ };
+
+ ClipEdges Edges() const {
+ return ClipEdges(*this, mVisIStartEdge, mVisIEndEdge);
+ }
+
+ static nsCharClipDisplayItem* CheckCast(nsDisplayItem* aItem) {
+ nsDisplayItem::Type t = aItem->GetType();
+ return (t == nsDisplayItem::TYPE_TEXT)
+ ? static_cast<nsCharClipDisplayItem*>(aItem) : nullptr;
+ }
+
+ // Lengths measured from the visual inline start and end sides
+ // (i.e. left and right respectively in horizontal writing modes,
+ // regardless of bidi directionality; top and bottom in vertical modes).
+ nscoord mVisIStartEdge;
+ nscoord mVisIEndEdge;
+ // Cached result of mFrame->IsSelected(). Only initialized when needed.
+ mutable mozilla::Maybe<bool> mIsFrameSelected;
+};
+
+namespace mozilla {
+
+class PaintTelemetry
+{
+ public:
+ enum class Metric {
+ DisplayList,
+ Layerization,
+ Rasterization,
+ COUNT,
+ };
+
+ class AutoRecord
+ {
+ public:
+ explicit AutoRecord(Metric aMetric);
+ ~AutoRecord();
+ private:
+ Metric mMetric;
+ mozilla::TimeStamp mStart;
+ };
+
+ class AutoRecordPaint
+ {
+ public:
+ AutoRecordPaint();
+ ~AutoRecordPaint();
+ private:
+ mozilla::TimeStamp mStart;
+ };
+
+ private:
+ static uint32_t sPaintLevel;
+ static uint32_t sMetricLevel;
+ static mozilla::EnumeratedArray<Metric, Metric::COUNT, double> sMetrics;
+};
+
+} // namespace mozilla
+
+#endif /*NSDISPLAYLIST_H_*/
diff --git a/layout/base/nsDisplayListInvalidation.cpp b/layout/base/nsDisplayListInvalidation.cpp
new file mode 100644
index 000000000..02907cd1f
--- /dev/null
+++ b/layout/base/nsDisplayListInvalidation.cpp
@@ -0,0 +1,153 @@
+/*-*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDisplayListInvalidation.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "nsTableFrame.h"
+
+nsDisplayItemGeometry::nsDisplayItemGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+{
+ MOZ_COUNT_CTOR(nsDisplayItemGeometry);
+ bool snap;
+ mBounds = aItem->GetBounds(aBuilder, &snap);
+}
+
+nsDisplayItemGeometry::~nsDisplayItemGeometry()
+{
+ MOZ_COUNT_DTOR(nsDisplayItemGeometry);
+}
+
+nsDisplayItemGenericGeometry::nsDisplayItemGenericGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , mBorderRect(aItem->GetBorderRect())
+{}
+
+bool
+ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder)
+{
+ return aBuilder->ShouldSyncDecodeImages();
+}
+
+void
+nsDisplayItemGenericGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mBorderRect.MoveBy(aOffset);
+}
+
+nsDisplayItemBoundsGeometry::nsDisplayItemBoundsGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+{
+ nscoord radii[8];
+ mHasRoundedCorners = aItem->Frame()->GetBorderRadii(radii);
+}
+
+nsDisplayBorderGeometry::nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+ , mContentRect(aItem->GetContentRect())
+{}
+
+void
+nsDisplayBorderGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mContentRect.MoveBy(aOffset);
+}
+
+nsDisplayBackgroundGeometry::nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem,
+ nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+ , mPositioningArea(aItem->GetPositioningArea())
+ , mDestRect(aItem->GetDestRect())
+{}
+
+void
+nsDisplayBackgroundGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPositioningArea.MoveBy(aOffset);
+ mDestRect.MoveBy(aOffset);
+}
+
+nsDisplayThemedBackgroundGeometry::nsDisplayThemedBackgroundGeometry(nsDisplayThemedBackground* aItem,
+ nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , mPositioningArea(aItem->GetPositioningArea())
+ , mWindowIsActive(aItem->IsWindowActive())
+{}
+
+void
+nsDisplayThemedBackgroundGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPositioningArea.MoveBy(aOffset);
+}
+
+nsDisplayBoxShadowInnerGeometry::nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , mPaddingRect(aItem->GetPaddingRect())
+{}
+
+void
+nsDisplayBoxShadowInnerGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mPaddingRect.MoveBy(aOffset);
+}
+
+nsDisplayBoxShadowOuterGeometry::nsDisplayBoxShadowOuterGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder, float aOpacity)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder)
+ , mOpacity(aOpacity)
+{}
+
+void
+nsDisplaySolidColorRegionGeometry::MoveBy(const nsPoint& aOffset)
+{
+ nsDisplayItemGeometry::MoveBy(aOffset);
+ mRegion.MoveBy(aOffset);
+}
+
+nsDisplaySVGEffectGeometry::nsDisplaySVGEffectGeometry(nsDisplaySVGEffects* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGeometry(aItem, aBuilder)
+ , mBBox(aItem->BBoxInUserSpace())
+ , mUserSpaceOffset(aItem->UserSpaceOffset())
+ , mFrameOffsetToReferenceFrame(aItem->ToReferenceFrame())
+{}
+
+void
+nsDisplaySVGEffectGeometry::MoveBy(const nsPoint& aOffset)
+{
+ mBounds.MoveBy(aOffset);
+ mFrameOffsetToReferenceFrame += aOffset;
+}
+
+nsDisplayMaskGeometry::nsDisplayMaskGeometry(nsDisplayMask* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+ , mDestRects(aItem->GetDestRects())
+{}
+
+nsDisplayFilterGeometry::nsDisplayFilterGeometry(nsDisplayFilter* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplaySVGEffectGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+{}
+
+nsCharClipGeometry::nsCharClipGeometry(nsCharClipDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder)
+ , mVisIStartEdge(aItem->mVisIStartEdge)
+ , mVisIEndEdge(aItem->mVisIEndEdge)
+{}
+
+nsDisplayTableItemGeometry::nsDisplayTableItemGeometry(nsDisplayTableItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+ , mFrameOffsetToViewport(aFrameOffsetToViewport)
+{}
diff --git a/layout/base/nsDisplayListInvalidation.h b/layout/base/nsDisplayListInvalidation.h
new file mode 100644
index 000000000..250ca94ce
--- /dev/null
+++ b/layout/base/nsDisplayListInvalidation.h
@@ -0,0 +1,322 @@
+/*-*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSDISPLAYLISTINVALIDATION_H_
+#define NSDISPLAYLISTINVALIDATION_H_
+
+#include "mozilla/Attributes.h"
+#include "FrameLayerBuilder.h"
+#include "imgIContainer.h"
+#include "nsRect.h"
+#include "nsColor.h"
+#include "gfxRect.h"
+
+class nsDisplayBackgroundImage;
+class nsCharClipDisplayItem;
+class nsDisplayItem;
+class nsDisplayListBuilder;
+class nsDisplayTableItem;
+class nsDisplayThemedBackground;
+class nsDisplaySVGEffects;
+class nsDisplayMask;
+class nsDisplayFilter;
+
+namespace mozilla {
+namespace gfx {
+struct Color;
+}
+}
+
+/**
+ * This stores the geometry of an nsDisplayItem, and the area
+ * that will be affected when painting the item.
+ *
+ * It is used to retain information about display items so they
+ * can be compared against new display items in the next paint.
+ */
+class nsDisplayItemGeometry
+{
+public:
+ nsDisplayItemGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+ virtual ~nsDisplayItemGeometry();
+
+ /**
+ * Compute the area required to be invalidated if this
+ * display item is removed.
+ */
+ const nsRect& ComputeInvalidationRegion() { return mBounds; }
+
+ /**
+ * Shifts all retained areas of the nsDisplayItemGeometry by the given offset.
+ *
+ * This is used to compensate for scrolling, since the destination buffer
+ * can scroll without requiring a full repaint.
+ *
+ * @param aOffset Offset to shift by.
+ */
+ virtual void MoveBy(const nsPoint& aOffset)
+ {
+ mBounds.MoveBy(aOffset);
+ }
+
+ /**
+ * Bounds of the display item
+ */
+ nsRect mBounds;
+};
+
+/**
+ * A default geometry implementation, used by nsDisplayItem. Retains
+ * and compares the bounds, and border rect.
+ *
+ * This should be sufficient for the majority of display items.
+ */
+class nsDisplayItemGenericGeometry : public nsDisplayItemGeometry
+{
+public:
+ nsDisplayItemGenericGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mBorderRect;
+};
+
+bool ShouldSyncDecodeImages(nsDisplayListBuilder* aBuilder);
+
+/**
+ * nsImageGeometryMixin is a mixin for geometry items that draw images.
+ * Geometry items that include this mixin can track drawing results and use
+ * that information to inform invalidation decisions.
+ *
+ * This mixin uses CRTP; its template parameter should be the type of the class
+ * that is inheriting from it. See nsDisplayItemGenericImageGeometry for an
+ * example.
+ */
+template <typename T>
+class nsImageGeometryMixin
+{
+public:
+ nsImageGeometryMixin(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
+ : mLastDrawResult(mozilla::image::DrawResult::NOT_READY)
+ , mWaitingForPaint(false)
+ {
+ // Transfer state from the previous version of this geometry item.
+ auto lastGeometry =
+ static_cast<T*>(mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem));
+ if (lastGeometry) {
+ mLastDrawResult = lastGeometry->mLastDrawResult;
+ mWaitingForPaint = lastGeometry->mWaitingForPaint;
+ }
+
+ // If our display item is going to invalidate to trigger sync decoding of
+ // images, mark ourselves as waiting for a paint. If we actually get
+ // painted, UpdateDrawResult will get called, and we'll clear the flag.
+ if (ShouldSyncDecodeImages(aBuilder) &&
+ ShouldInvalidateToSyncDecodeImages()) {
+ mWaitingForPaint = true;
+ }
+ }
+
+ static void UpdateDrawResult(nsDisplayItem* aItem,
+ mozilla::image::DrawResult aResult)
+ {
+ auto lastGeometry =
+ static_cast<T*>(mozilla::FrameLayerBuilder::GetMostRecentGeometry(aItem));
+ if (lastGeometry) {
+ lastGeometry->mLastDrawResult = aResult;
+ lastGeometry->mWaitingForPaint = false;
+ }
+ }
+
+ bool ShouldInvalidateToSyncDecodeImages() const
+ {
+ if (mWaitingForPaint) {
+ // We previously invalidated for sync decoding and haven't gotten painted
+ // since them. This suggests that our display item is completely occluded
+ // and there's no point in invalidating again - and because the reftest
+ // harness takes a new snapshot every time we invalidate, doing so might
+ // lead to an invalidation loop if we're in a reftest.
+ return false;
+ }
+
+ if (mLastDrawResult == mozilla::image::DrawResult::SUCCESS ||
+ mLastDrawResult == mozilla::image::DrawResult::BAD_IMAGE) {
+ return false;
+ }
+
+ return true;
+ }
+
+private:
+ mozilla::image::DrawResult mLastDrawResult;
+ bool mWaitingForPaint;
+};
+
+/**
+ * nsDisplayItemGenericImageGeometry is a generic geometry item class that
+ * includes nsImageGeometryMixin.
+ *
+ * This should be sufficient for most display items that draw images.
+ */
+class nsDisplayItemGenericImageGeometry
+ : public nsDisplayItemGenericGeometry
+ , public nsImageGeometryMixin<nsDisplayItemGenericImageGeometry>
+{
+public:
+ nsDisplayItemGenericImageGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder)
+ : nsDisplayItemGenericGeometry(aItem, aBuilder)
+ , nsImageGeometryMixin(aItem, aBuilder)
+ { }
+};
+
+class nsDisplayItemBoundsGeometry : public nsDisplayItemGeometry
+{
+public:
+ nsDisplayItemBoundsGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+
+ bool mHasRoundedCorners;
+};
+
+class nsDisplayBorderGeometry
+ : public nsDisplayItemGeometry
+ , public nsImageGeometryMixin<nsDisplayBorderGeometry>
+{
+public:
+ nsDisplayBorderGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mContentRect;
+};
+
+class nsDisplayBackgroundGeometry
+ : public nsDisplayItemGeometry
+ , public nsImageGeometryMixin<nsDisplayBackgroundGeometry>
+{
+public:
+ nsDisplayBackgroundGeometry(nsDisplayBackgroundImage* aItem, nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPositioningArea;
+ nsRect mDestRect;
+};
+
+class nsDisplayThemedBackgroundGeometry : public nsDisplayItemGeometry
+{
+public:
+ nsDisplayThemedBackgroundGeometry(nsDisplayThemedBackground* aItem, nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPositioningArea;
+ bool mWindowIsActive;
+};
+
+class nsDisplayBoxShadowInnerGeometry : public nsDisplayItemGeometry
+{
+public:
+ nsDisplayBoxShadowInnerGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRect mPaddingRect;
+};
+
+class nsDisplayBoxShadowOuterGeometry : public nsDisplayItemGenericGeometry
+{
+public:
+ nsDisplayBoxShadowOuterGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ float aOpacity);
+
+ float mOpacity;
+};
+
+class nsDisplaySolidColorGeometry : public nsDisplayItemBoundsGeometry
+{
+public:
+ nsDisplaySolidColorGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ nscolor aColor)
+ : nsDisplayItemBoundsGeometry(aItem, aBuilder)
+ , mColor(aColor)
+ { }
+
+ nscolor mColor;
+};
+
+class nsDisplaySolidColorRegionGeometry : public nsDisplayItemBoundsGeometry
+{
+public:
+ nsDisplaySolidColorRegionGeometry(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsRegion& aRegion,
+ mozilla::gfx::Color aColor)
+ : nsDisplayItemBoundsGeometry(aItem, aBuilder)
+ , mRegion(aRegion)
+ , mColor(aColor)
+ { }
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ nsRegion mRegion;
+ mozilla::gfx::Color mColor;
+};
+
+class nsDisplaySVGEffectGeometry : public nsDisplayItemGeometry
+{
+public:
+ nsDisplaySVGEffectGeometry(nsDisplaySVGEffects* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ virtual void MoveBy(const nsPoint& aOffset) override;
+
+ gfxRect mBBox;
+ gfxPoint mUserSpaceOffset;
+ nsPoint mFrameOffsetToReferenceFrame;
+};
+
+class nsDisplayMaskGeometry : public nsDisplaySVGEffectGeometry
+ , public nsImageGeometryMixin<nsDisplayMaskGeometry>
+{
+public:
+ nsDisplayMaskGeometry(nsDisplayMask* aItem, nsDisplayListBuilder* aBuilder);
+
+ nsTArray<nsRect> mDestRects;
+};
+
+class nsDisplayFilterGeometry : public nsDisplaySVGEffectGeometry
+ , public nsImageGeometryMixin<nsDisplayFilterGeometry>
+{
+public:
+ nsDisplayFilterGeometry(nsDisplayFilter* aItem,
+ nsDisplayListBuilder* aBuilder);
+};
+
+class nsCharClipGeometry : public nsDisplayItemGenericGeometry
+{
+public:
+ nsCharClipGeometry(nsCharClipDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
+
+ nscoord mVisIStartEdge;
+ nscoord mVisIEndEdge;
+};
+
+class nsDisplayTableItemGeometry
+ : public nsDisplayItemGenericGeometry
+ , public nsImageGeometryMixin<nsDisplayTableItemGeometry>
+{
+public:
+ nsDisplayTableItemGeometry(nsDisplayTableItem* aItem,
+ nsDisplayListBuilder* aBuilder,
+ const nsPoint& aFrameOffsetToViewport);
+
+ nsPoint mFrameOffsetToViewport;
+};
+
+#endif /*NSDISPLAYLISTINVALIDATION_H_*/
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
new file mode 100644
index 000000000..a1105ae52
--- /dev/null
+++ b/layout/base/nsDocumentViewer.cpp
@@ -0,0 +1,4668 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* container for a document and its presentation */
+
+#include "mozilla/ServoStyleSet.h"
+#include "nsAutoPtr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIContent.h"
+#include "nsIContentViewerContainer.h"
+#include "nsIContentViewer.h"
+#include "nsIDocumentViewerPrint.h"
+#include "nsIDOMBeforeUnloadEvent.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsIFrame.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsSubDocumentFrame.h"
+
+#include "nsILinkHandler.h"
+#include "nsIDOMDocument.h"
+#include "nsISelectionListener.h"
+#include "mozilla/dom/Selection.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsContentUtils.h"
+#include "nsLayoutStylesheetCache.h"
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/DocAccessible.h"
+#endif
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+#include "nsViewManager.h"
+#include "nsView.h"
+
+#include "nsIPageSequenceFrame.h"
+#include "nsNetUtil.h"
+#include "nsIContentViewerEdit.h"
+#include "nsIContentViewerFile.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsDocShell.h"
+#include "nsIBaseWindow.h"
+#include "nsILayoutHistoryState.h"
+#include "nsCharsetSource.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIImageLoadingContent.h"
+#include "nsCopySupport.h"
+#include "nsIDOMHTMLFrameSetElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#include "nsXULPopupManager.h"
+#endif
+
+#include "nsIClipboardHelper.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindow.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsPIWindowRoot.h"
+#include "nsJSEnvironment.h"
+#include "nsFocusManager.h"
+
+#include "nsIScrollableFrame.h"
+#include "nsStyleSheetService.h"
+#include "nsRenderingContext.h"
+#include "nsILoadContext.h"
+
+#include "nsIPrompt.h"
+#include "imgIContainer.h" // image animation mode constants
+
+#include "nsSandboxFlags.h"
+
+#include "mozilla/DocLoadingTimelineMarker.h"
+
+//--------------------------
+// Printing Include
+//---------------------------
+#ifdef NS_PRINTING
+
+#include "nsIWebBrowserPrint.h"
+
+#include "nsPrintEngine.h"
+
+// Print Options
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsIPluginDocument.h"
+
+#endif // NS_PRINTING
+
+//focus
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMEventListener.h"
+#include "nsISelectionController.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "nsISHEntry.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIWebNavigation.h"
+#include "mozilla/dom/XMLHttpRequestMainThread.h"
+
+//paint forcing
+#include <stdio.h>
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define BEFOREUNLOAD_DISABLED_PREFNAME "dom.disable_beforeunload"
+#define BEFOREUNLOAD_REQUIRES_INTERACTION_PREFNAME "dom.require_user_interaction_for_beforeunload"
+
+//-----------------------------------------------------
+// LOGGING
+#include "LayoutLogging.h"
+#include "mozilla/Logging.h"
+
+#ifdef NS_PRINTING
+static mozilla::LazyLogModule gPrintingLog("printing");
+
+#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
+#endif // NS_PRINTING
+
+#define PRT_YESNO(_p) ((_p)?"YES":"NO")
+//-----------------------------------------------------
+
+class nsDocumentViewer;
+namespace mozilla {
+class AutoPrintEventDispatcher;
+}
+
+// a small delegate class used to avoid circular references
+
+class nsDocViewerSelectionListener : public nsISelectionListener
+{
+public:
+
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // nsISelectionListerner interface
+ NS_DECL_NSISELECTIONLISTENER
+
+ nsDocViewerSelectionListener()
+ : mDocViewer(nullptr)
+ , mSelectionWasCollapsed(true)
+ {
+ }
+
+ nsresult Init(nsDocumentViewer *aDocViewer);
+
+ void Disconnect() { mDocViewer = nullptr; }
+
+protected:
+
+ virtual ~nsDocViewerSelectionListener() {}
+
+ nsDocumentViewer* mDocViewer;
+ bool mSelectionWasCollapsed;
+
+};
+
+
+/** editor Implementation of the FocusListener interface
+ */
+class nsDocViewerFocusListener : public nsIDOMEventListener
+{
+public:
+ /** default constructor
+ */
+ nsDocViewerFocusListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsresult Init(nsDocumentViewer *aDocViewer);
+
+ void Disconnect() { mDocViewer = nullptr; }
+
+protected:
+ /** default destructor
+ */
+ virtual ~nsDocViewerFocusListener();
+
+private:
+ nsDocumentViewer* mDocViewer;
+};
+
+
+//-------------------------------------------------------------
+class nsDocumentViewer final : public nsIContentViewer,
+ public nsIContentViewerEdit,
+ public nsIContentViewerFile,
+ public nsIDocumentViewerPrint
+
+#ifdef NS_PRINTING
+ , public nsIWebBrowserPrint
+#endif
+
+{
+ friend class nsDocViewerSelectionListener;
+ friend class nsPagePrintTimer;
+ friend class nsPrintEngine;
+
+public:
+ nsDocumentViewer();
+
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // nsIContentViewer interface...
+ NS_DECL_NSICONTENTVIEWER
+
+ // nsIContentViewerEdit
+ NS_DECL_NSICONTENTVIEWEREDIT
+
+ // nsIContentViewerFile
+ NS_DECL_NSICONTENTVIEWERFILE
+
+#ifdef NS_PRINTING
+ // nsIWebBrowserPrint
+ NS_DECL_NSIWEBBROWSERPRINT
+#endif
+
+ typedef void (*CallChildFunc)(nsIContentViewer* aViewer, void* aClosure);
+ void CallChildren(CallChildFunc aFunc, void* aClosure);
+
+ // nsIDocumentViewerPrint Printing Methods
+ NS_DECL_NSIDOCUMENTVIEWERPRINT
+
+protected:
+ virtual ~nsDocumentViewer();
+
+private:
+ /**
+ * Creates a view manager, root view, and widget for the root view, setting
+ * mViewManager and mWindow.
+ * @param aSize the initial size in appunits
+ * @param aContainerView the container view to hook our root view up
+ * to as a child, or null if this will be the root view manager
+ */
+ nsresult MakeWindow(const nsSize& aSize, nsView* aContainerView);
+
+ /**
+ * Create our device context
+ */
+ nsresult CreateDeviceContext(nsView* aContainerView);
+
+ /**
+ * If aDoCreation is true, this creates the device context, creates a
+ * prescontext if necessary, and calls MakeWindow.
+ *
+ * If aForceSetNewDocument is false, then SetNewDocument won't be
+ * called if the window's current document is already mDocument.
+ */
+ nsresult InitInternal(nsIWidget* aParentWidget,
+ nsISupports *aState,
+ const nsIntRect& aBounds,
+ bool aDoCreation,
+ bool aNeedMakeCX = true,
+ bool aForceSetNewDocument = true);
+ /**
+ * @param aDoInitialReflow set to true if you want to kick off the initial
+ * reflow
+ */
+ nsresult InitPresentationStuff(bool aDoInitialReflow);
+
+ nsresult GetPopupNode(nsIDOMNode** aNode);
+ nsresult GetPopupLinkNode(nsIDOMNode** aNode);
+ nsresult GetPopupImageNode(nsIImageLoadingContent** aNode);
+
+ nsresult GetContentSizeInternal(int32_t* aWidth, int32_t* aHeight,
+ nscoord aMaxWidth, nscoord aMaxHeight);
+
+ void PrepareToStartLoad(void);
+
+ nsresult SyncParentSubDocMap();
+
+ mozilla::dom::Selection* GetDocumentSelection();
+
+ void DestroyPresShell();
+ void DestroyPresContext();
+
+#ifdef NS_PRINTING
+ // Called when the DocViewer is notified that the state
+ // of Printing or PP has changed
+ void SetIsPrintingInDocShellTree(nsIDocShellTreeItem* aParentNode,
+ bool aIsPrintingOrPP,
+ bool aStartAtTop);
+#endif // NS_PRINTING
+
+ // Whether we should attach to the top level widget. This is true if we
+ // are sharing/recycling a single base widget and not creating multiple
+ // child widgets.
+ bool ShouldAttachToTopLevel();
+
+protected:
+ // These return the current shell/prescontext etc.
+ nsIPresShell* GetPresShell();
+ nsPresContext* GetPresContext();
+ nsViewManager* GetViewManager();
+
+ void DetachFromTopLevelWidget();
+
+ // IMPORTANT: The ownership implicit in the following member
+ // variables has been explicitly checked and set using nsCOMPtr
+ // for owning pointers and raw COM interface pointers for weak
+ // (ie, non owning) references. If you add any members to this
+ // class, please make the ownership explicit (pinkerton, scc).
+
+ WeakPtr<nsDocShell> mContainer; // it owns me!
+ nsWeakPtr mTopContainerWhilePrinting;
+ RefPtr<nsDeviceContext> mDeviceContext; // We create and own this baby
+
+ // the following six items are explicitly in this order
+ // so they will be destroyed in the reverse order (pinkerton, scc)
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsIWidget> mWindow; // may be null
+ RefPtr<nsViewManager> mViewManager;
+ RefPtr<nsPresContext> mPresContext;
+ nsCOMPtr<nsIPresShell> mPresShell;
+
+ RefPtr<nsDocViewerSelectionListener> mSelectionListener;
+ RefPtr<nsDocViewerFocusListener> mFocusListener;
+
+ nsCOMPtr<nsIContentViewer> mPreviousViewer;
+ nsCOMPtr<nsISHEntry> mSHEntry;
+
+ nsIWidget* mParentWidget; // purposely won't be ref counted. May be null
+ bool mAttachedToParent; // view is attached to the parent widget
+
+ nsIntRect mBounds;
+
+ // mTextZoom/mPageZoom record the textzoom/pagezoom of the first (galley)
+ // presshell only.
+ float mTextZoom; // Text zoom, defaults to 1.0
+ float mPageZoom;
+ float mOverrideDPPX; // DPPX overrided, defaults to 0.0
+ int mMinFontSize;
+
+ int16_t mNumURLStarts;
+ int16_t mDestroyRefCount; // a second "refcount" for the document viewer's "destroy"
+
+ unsigned mStopped : 1;
+ unsigned mLoaded : 1;
+ unsigned mDeferredWindowClose : 1;
+ // document management data
+ // these items are specific to markup documents (html and xml)
+ // may consider splitting these out into a subclass
+ unsigned mIsSticky : 1;
+ unsigned mInPermitUnload : 1;
+ unsigned mInPermitUnloadPrompt: 1;
+
+#ifdef NS_PRINTING
+ unsigned mClosingWhilePrinting : 1;
+
+#if NS_PRINT_PREVIEW
+ unsigned mPrintPreviewZoomed : 1;
+
+ // These data members support delayed printing when the document is loading
+ unsigned mPrintIsPending : 1;
+ unsigned mPrintDocIsFullyLoaded : 1;
+ nsCOMPtr<nsIPrintSettings> mCachedPrintSettings;
+ nsCOMPtr<nsIWebProgressListener> mCachedPrintWebProgressListner;
+
+ RefPtr<nsPrintEngine> mPrintEngine;
+ float mOriginalPrintPreviewScale;
+ float mPrintPreviewZoom;
+ nsAutoPtr<AutoPrintEventDispatcher> mAutoBeforeAndAfterPrint;
+#endif // NS_PRINT_PREVIEW
+
+#ifdef DEBUG
+ FILE* mDebugFile;
+#endif // DEBUG
+#endif // NS_PRINTING
+
+ /* character set member data */
+ int32_t mHintCharsetSource;
+ nsCString mHintCharset;
+ nsCString mForceCharacterSet;
+
+ bool mIsPageMode;
+ bool mInitializedForPrintPreview;
+ bool mHidden;
+};
+
+namespace mozilla {
+
+/**
+ * A RAII class for automatic dispatch of the 'beforeprint' and 'afterprint'
+ * events ('beforeprint' on construction, 'afterprint' on destruction).
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/Events/beforeprint
+ * https://developer.mozilla.org/en-US/docs/Web/Events/afterprint
+ */
+class AutoPrintEventDispatcher
+{
+public:
+ explicit AutoPrintEventDispatcher(nsIDocument* aTop) : mTop(aTop)
+ {
+ DispatchEventToWindowTree(NS_LITERAL_STRING("beforeprint"));
+ }
+ ~AutoPrintEventDispatcher()
+ {
+ DispatchEventToWindowTree(NS_LITERAL_STRING("afterprint"));
+ }
+
+private:
+ void DispatchEventToWindowTree(const nsAString& aEvent)
+ {
+ nsCOMArray<nsIDocument> targets;
+ CollectDocuments(mTop, &targets);
+ for (int32_t i = 0; i < targets.Count(); ++i) {
+ nsIDocument* d = targets[i];
+ nsContentUtils::DispatchTrustedEvent(d, d->GetWindow(),
+ aEvent, false, false, nullptr);
+ }
+ }
+
+ static bool CollectDocuments(nsIDocument* aDocument, void* aData)
+ {
+ if (aDocument) {
+ static_cast<nsCOMArray<nsIDocument>*>(aData)->AppendObject(aDocument);
+ aDocument->EnumerateSubDocuments(CollectDocuments, aData);
+ }
+ return true;
+ }
+
+ nsCOMPtr<nsIDocument> mTop;
+};
+
+}
+
+class nsDocumentShownDispatcher : public Runnable
+{
+public:
+ explicit nsDocumentShownDispatcher(nsCOMPtr<nsIDocument> aDocument)
+ : mDocument(aDocument) {}
+
+ NS_IMETHOD Run() override;
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+
+//------------------------------------------------------------------
+// nsDocumentViewer
+//------------------------------------------------------------------
+
+//------------------------------------------------------------------
+already_AddRefed<nsIContentViewer>
+NS_NewContentViewer()
+{
+ RefPtr<nsDocumentViewer> viewer = new nsDocumentViewer();
+ return viewer.forget();
+}
+
+void nsDocumentViewer::PrepareToStartLoad()
+{
+ mStopped = false;
+ mLoaded = false;
+ mAttachedToParent = false;
+ mDeferredWindowClose = false;
+
+#ifdef NS_PRINTING
+ mPrintIsPending = false;
+ mPrintDocIsFullyLoaded = false;
+ mClosingWhilePrinting = false;
+
+ // Make sure we have destroyed it and cleared the data member
+ if (mPrintEngine) {
+ mPrintEngine->Destroy();
+ mPrintEngine = nullptr;
+#ifdef NS_PRINT_PREVIEW
+ SetIsPrintPreview(false);
+#endif
+ }
+
+#ifdef DEBUG
+ mDebugFile = nullptr;
+#endif
+
+#endif // NS_PRINTING
+}
+
+// Note: operator new zeros our memory, so no need to init things to null.
+nsDocumentViewer::nsDocumentViewer()
+ : mTextZoom(1.0), mPageZoom(1.0), mOverrideDPPX(0.0), mMinFontSize(0),
+ mIsSticky(true),
+#ifdef NS_PRINT_PREVIEW
+ mPrintPreviewZoom(1.0),
+#endif
+ mHintCharsetSource(kCharsetUninitialized),
+ mInitializedForPrintPreview(false),
+ mHidden(false)
+{
+ PrepareToStartLoad();
+}
+
+NS_IMPL_ADDREF(nsDocumentViewer)
+NS_IMPL_RELEASE(nsDocumentViewer)
+
+NS_INTERFACE_MAP_BEGIN(nsDocumentViewer)
+ NS_INTERFACE_MAP_ENTRY(nsIContentViewer)
+ NS_INTERFACE_MAP_ENTRY(nsIContentViewerFile)
+ NS_INTERFACE_MAP_ENTRY(nsIContentViewerEdit)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerPrint)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentViewer)
+#ifdef NS_PRINTING
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPrint)
+#endif
+NS_INTERFACE_MAP_END
+
+nsDocumentViewer::~nsDocumentViewer()
+{
+ if (mDocument) {
+ Close(nullptr);
+ mDocument->Destroy();
+ }
+
+ NS_ASSERTION(!mPresShell && !mPresContext,
+ "User did not call nsIContentViewer::Destroy");
+ if (mPresShell || mPresContext) {
+ // Make sure we don't hand out a reference to the content viewer to
+ // the SHEntry!
+ mSHEntry = nullptr;
+
+ Destroy();
+ }
+
+ if (mSelectionListener) {
+ mSelectionListener->Disconnect();
+ }
+
+ if (mFocusListener) {
+ mFocusListener->Disconnect();
+ }
+
+ // XXX(?) Revoke pending invalidate events
+}
+
+/*
+ * This method is called by the Document Loader once a document has
+ * been created for a particular data stream... The content viewer
+ * must cache this document for later use when Init(...) is called.
+ *
+ * This method is also called when an out of band document.write() happens.
+ * In that case, the document passed in is the same as the previous document.
+ */
+/* virtual */ void
+nsDocumentViewer::LoadStart(nsIDocument* aDocument)
+{
+ MOZ_ASSERT(aDocument);
+
+ if (!mDocument) {
+ mDocument = aDocument;
+ }
+}
+
+nsresult
+nsDocumentViewer::SyncParentSubDocMap()
+{
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (!docShell) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
+ if (!mDocument || !pwin) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<Element> element = pwin->GetFrameElementInternal();
+ if (!element) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShell->GetParent(getter_AddRefs(parent));
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent_win = parent ? parent->GetWindow() : nullptr;
+ if (!parent_win) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> parent_doc = parent_win->GetDoc();
+ if (!parent_doc) {
+ return NS_OK;
+ }
+
+ if (mDocument &&
+ parent_doc->GetSubDocumentFor(element) != mDocument &&
+ parent_doc->EventHandlingSuppressed()) {
+ mDocument->SuppressEventHandling(nsIDocument::eEvents,
+ parent_doc->EventHandlingSuppressed());
+ }
+ return parent_doc->SetSubDocumentFor(element, mDocument);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetContainer(nsIDocShell* aContainer)
+{
+ mContainer = static_cast<nsDocShell*>(aContainer);
+ if (mPresContext) {
+ mPresContext->SetContainer(mContainer);
+ }
+
+ // We're loading a new document into the window where this document
+ // viewer lives, sync the parent document's frame element -> sub
+ // document map
+
+ return SyncParentSubDocMap();
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetContainer(nsIDocShell** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIDocShell> container(mContainer);
+ container.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Init(nsIWidget* aParentWidget,
+ const nsIntRect& aBounds)
+{
+ return InitInternal(aParentWidget, nullptr, aBounds, true);
+}
+
+nsresult
+nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow)
+{
+ // We assert this because initializing the pres shell could otherwise cause
+ // re-entrancy into nsDocumentViewer methods, which might cause a different
+ // pres shell to be created. Callers of InitPresentationStuff should ensure
+ // the call is appropriately bounded by an nsAutoScriptBlocker to decide
+ // when it is safe for these re-entrant calls to be made.
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "InitPresentationStuff must only be called when scripts are "
+ "blocked");
+
+ if (GetIsPrintPreview())
+ return NS_OK;
+
+ NS_ASSERTION(!mPresShell,
+ "Someone should have destroyed the presshell!");
+
+ // Create the style set...
+ StyleSetHandle styleSet = CreateStyleSet(mDocument);
+
+ // Now make the shell for the document
+ mPresShell = mDocument->CreateShell(mPresContext, mViewManager, styleSet);
+ if (!mPresShell) {
+ styleSet->Delete();
+ return NS_ERROR_FAILURE;
+ }
+
+ // We're done creating the style set
+ styleSet->EndUpdate();
+
+ if (aDoInitialReflow) {
+ // Since Initialize() will create frames for *all* items
+ // that are currently in the document tree, we need to flush
+ // any pending notifications to prevent the content sink from
+ // duplicating layout frames for content it has added to the tree
+ // but hasn't notified the document about. (Bug 154018)
+ //
+ // Note that we are flushing before we add mPresShell as an observer
+ // to avoid bogus notifications.
+
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+
+ mPresShell->BeginObservingDocument();
+
+ // Initialize our view manager
+ int32_t p2a = mPresContext->AppUnitsPerDevPixel();
+ MOZ_ASSERT(p2a ==
+ mPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
+ nscoord width = p2a * mBounds.width;
+ nscoord height = p2a * mBounds.height;
+
+ mViewManager->SetWindowDimensions(width, height);
+ mPresContext->SetTextZoom(mTextZoom);
+ mPresContext->SetFullZoom(mPageZoom);
+ mPresContext->SetOverrideDPPX(mOverrideDPPX);
+ mPresContext->SetBaseMinFontSize(mMinFontSize);
+
+ p2a = mPresContext->AppUnitsPerDevPixel(); // zoom may have changed it
+ width = p2a * mBounds.width;
+ height = p2a * mBounds.height;
+ if (aDoInitialReflow) {
+ nsCOMPtr<nsIPresShell> shell = mPresShell;
+ // Initial reflow
+ shell->Initialize(width, height);
+ } else {
+ // Store the visible area so it's available for other callers of
+ // Initialize, like nsContentSink::StartLayout.
+ mPresContext->SetVisibleArea(nsRect(0, 0, width, height));
+ }
+
+ // now register ourselves as a selection listener, so that we get
+ // called when the selection changes in the window
+ if (!mSelectionListener) {
+ nsDocViewerSelectionListener *selectionListener =
+ new nsDocViewerSelectionListener();
+
+ selectionListener->Init(this);
+
+ // mSelectionListener is a owning reference
+ mSelectionListener = selectionListener;
+ }
+
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = selection->AddSelectionListener(mSelectionListener);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Save old listener so we can unregister it
+ RefPtr<nsDocViewerFocusListener> oldFocusListener = mFocusListener;
+ if (oldFocusListener) {
+ oldFocusListener->Disconnect();
+ }
+
+ // focus listener
+ //
+ // now register ourselves as a focus listener, so that we get called
+ // when the focus changes in the window
+ nsDocViewerFocusListener *focusListener = new nsDocViewerFocusListener();
+
+ focusListener->Init(this);
+
+ // mFocusListener is a strong reference
+ mFocusListener = focusListener;
+
+ if (mDocument) {
+ mDocument->AddEventListener(NS_LITERAL_STRING("focus"),
+ mFocusListener,
+ false, false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("blur"),
+ mFocusListener,
+ false, false);
+
+ if (oldFocusListener) {
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"),
+ oldFocusListener, false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"),
+ oldFocusListener, false);
+ }
+ }
+
+ if (aDoInitialReflow && mDocument) {
+ mDocument->ScrollToRef();
+ }
+
+ return NS_OK;
+}
+
+static nsPresContext*
+CreatePresContext(nsIDocument* aDocument,
+ nsPresContext::nsPresContextType aType,
+ nsView* aContainerView)
+{
+ if (aContainerView)
+ return new nsPresContext(aDocument, aType);
+ return new nsRootPresContext(aDocument, aType);
+}
+
+//-----------------------------------------------
+// This method can be used to initial the "presentation"
+// The aDoCreation indicates whether it should create
+// all the new objects or just initialize the existing ones
+nsresult
+nsDocumentViewer::InitInternal(nsIWidget* aParentWidget,
+ nsISupports *aState,
+ const nsIntRect& aBounds,
+ bool aDoCreation,
+ bool aNeedMakeCX /*= true*/,
+ bool aForceSetNewDocument /* = true*/)
+{
+ if (mIsPageMode) {
+ // XXXbz should the InitInternal in SetPageMode just pass false
+ // here itself?
+ aForceSetNewDocument = false;
+ }
+
+ // We don't want any scripts to run here. That can cause flushing,
+ // which can cause reentry into initialization of this document viewer,
+ // which would be disastrous.
+ nsAutoScriptBlocker blockScripts;
+
+ mParentWidget = aParentWidget; // not ref counted
+ mBounds = aBounds;
+
+ nsresult rv = NS_OK;
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
+
+ nsView* containerView = FindContainerView();
+
+ bool makeCX = false;
+ if (aDoCreation) {
+ nsresult rv = CreateDeviceContext(containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXXbz this is a nasty hack to do with the fact that we create
+ // presentations both in Init() and in Show()... Ideally we would only do
+ // it in one place (Show()) and require that callers call init(), open(),
+ // show() in that order or something.
+ if (!mPresContext &&
+ (aParentWidget || containerView || mDocument->IsBeingUsedAsImage() ||
+ (mDocument->GetDisplayDocument() &&
+ mDocument->GetDisplayDocument()->GetShell()))) {
+ // Create presentation context
+ if (mIsPageMode) {
+ //Presentation context already created in SetPageMode which is calling this method
+ } else {
+ mPresContext = CreatePresContext(mDocument,
+ nsPresContext::eContext_Galley, containerView);
+ }
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = mPresContext->Init(mDeviceContext);
+ if (NS_FAILED(rv)) {
+ mPresContext = nullptr;
+ return rv;
+ }
+
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ makeCX = !GetIsPrintPreview() && aNeedMakeCX; // needs to be true except when we are already in PP or we are enabling/disabling paginated mode.
+#else
+ makeCX = true;
+#endif
+ }
+
+ if (mPresContext) {
+ // Create the ViewManager and Root View...
+
+ // We must do this before we tell the script global object about
+ // this new document since doing that will cause us to re-enter
+ // into nsSubDocumentFrame code through reflows caused by
+ // FlushPendingNotifications() calls down the road...
+
+ rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(aBounds.width),
+ mPresContext->DevPixelsToAppUnits(aBounds.height)),
+ containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+ Hide();
+
+#ifdef NS_PRINT_PREVIEW
+ if (mIsPageMode) {
+ // I'm leaving this in a broken state for the moment; we should
+ // be measuring/scaling with the print device context, not the
+ // screen device context, but this is good enough to allow
+ // printing reftests to work.
+ double pageWidth = 0, pageHeight = 0;
+ mPresContext->GetPrintSettings()->GetEffectivePageSize(&pageWidth,
+ &pageHeight);
+ mPresContext->SetPageSize(
+ nsSize(mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageWidth)),
+ mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageHeight))));
+ mPresContext->SetIsRootPaginatedDocument(true);
+ mPresContext->SetPageScale(1.0f);
+ }
+#endif
+ } else {
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> requestor(mContainer);
+ if (requestor) {
+ if (mPresContext) {
+ nsCOMPtr<nsILinkHandler> linkHandler;
+ requestor->GetInterface(NS_GET_IID(nsILinkHandler),
+ getter_AddRefs(linkHandler));
+
+ mPresContext->SetContainer(mContainer);
+ mPresContext->SetLinkHandler(linkHandler);
+ }
+
+ // Set script-context-owner in the document
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(requestor);
+
+ if (window) {
+ nsCOMPtr<nsIDocument> curDoc = window->GetExtantDoc();
+ if (aForceSetNewDocument || curDoc != mDocument) {
+ rv = window->SetNewDocument(mDocument, aState, false);
+ if (NS_FAILED(rv)) {
+ Destroy();
+ return rv;
+ }
+ nsJSContext::LoadStart();
+ }
+ }
+ }
+
+ if (aDoCreation && mPresContext) {
+ // The ViewManager and Root View was created above (in
+ // MakeWindow())...
+
+ rv = InitPresentationStuff(!makeCX);
+ }
+
+ return rv;
+}
+
+void nsDocumentViewer::SetNavigationTiming(nsDOMNavigationTiming* timing)
+{
+ NS_ASSERTION(mDocument, "Must have a document to set navigation timing.");
+ if (mDocument) {
+ mDocument->SetNavigationTiming(timing);
+ }
+}
+
+//
+// LoadComplete(aStatus)
+//
+// aStatus - The status returned from loading the document.
+//
+// This method is called by the container when the document has been
+// completely loaded.
+//
+NS_IMETHODIMP
+nsDocumentViewer::LoadComplete(nsresult aStatus)
+{
+ /* We need to protect ourself against auto-destruction in case the
+ window is closed while processing the OnLoad event. See bug
+ http://bugzilla.mozilla.org/show_bug.cgi?id=78445 for more
+ explanation.
+ */
+ RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
+
+ // Flush out layout so it's up-to-date by the time onload is called.
+ // Note that this could destroy the window, so do this before
+ // checking for our mDocument and its window.
+ if (mPresShell && !mStopped) {
+ // Hold strong ref because this could conceivably run script
+ nsCOMPtr<nsIPresShell> shell = mPresShell;
+ shell->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsresult rv = NS_OK;
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ // First, get the window from the document...
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+
+ mLoaded = true;
+
+ // Now, fire either an OnLoad or OnError event to the document...
+ bool restoring = false;
+ // XXXbz imagelib kills off the document load for a full-page image with
+ // NS_ERROR_PARSED_DATA_CACHED if it's in the cache. So we want to treat
+ // that one as a success code; otherwise whether we fire onload for the image
+ // will depend on whether it's cached!
+ if(window &&
+ (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ // XXX Dispatching to |window|, but using |document| as the target.
+ event.mTarget = mDocument;
+
+ // If the document presentation is being restored, we don't want to fire
+ // onload to the document content since that would likely confuse scripts
+ // on the page.
+
+ nsIDocShell *docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+
+ docShell->GetRestoringDocument(&restoring);
+ if (!restoring) {
+ NS_ASSERTION(mDocument->IsXULDocument() || // readyState for XUL is bogus
+ mDocument->GetReadyStateEnum() ==
+ nsIDocument::READYSTATE_INTERACTIVE ||
+ // test_stricttransportsecurity.html has old-style
+ // docshell-generated about:blank docs reach this code!
+ (mDocument->GetReadyStateEnum() ==
+ nsIDocument::READYSTATE_UNINITIALIZED &&
+ NS_IsAboutBlank(mDocument->GetDocumentURI())),
+ "Bad readystate");
+ nsCOMPtr<nsIDocument> d = mDocument;
+ mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
+
+ RefPtr<nsDOMNavigationTiming> timing(d->GetNavigationTiming());
+ if (timing) {
+ timing->NotifyLoadEventStart();
+ }
+
+ // Dispatch observer notification to notify observers document load is complete.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal *principal = d->NodePrincipal();
+ os->NotifyObservers(d,
+ nsContentUtils::IsSystemPrincipal(principal) ?
+ "chrome-document-loaded" :
+ "content-document-loaded",
+ nullptr);
+ }
+
+ // Notify any devtools about the load.
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+ if (timelines && timelines->HasConsumer(docShell)) {
+ timelines->AddMarkerForDocShell(docShell,
+ MakeUnique<DocLoadingTimelineMarker>("document::Load"));
+ }
+
+ EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+ if (timing) {
+ timing->NotifyLoadEventEnd();
+ }
+ }
+ } else {
+ // XXX: Should fire error event to the document...
+ }
+
+ // Notify the document that it has been shown (regardless of whether
+ // it was just loaded). Note: mDocument may be null now if the above
+ // firing of onload caused the document to unload.
+ if (mDocument) {
+ // Re-get window, since it might have changed during above firing of onload
+ window = mDocument->GetWindow();
+ if (window) {
+ nsIDocShell *docShell = window->GetDocShell();
+ bool isInUnload;
+ if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) &&
+ !isInUnload) {
+ mDocument->OnPageShow(restoring, nullptr);
+ }
+ }
+ }
+
+ if (!mStopped) {
+ if (mDocument) {
+ mDocument->ScrollToRef();
+ }
+
+ // Now that the document has loaded, we can tell the presshell
+ // to unsuppress painting.
+ if (mPresShell) {
+ nsCOMPtr<nsIPresShell> shell(mPresShell);
+ shell->UnsuppressPainting();
+ // mPresShell could have been removed now, see bug 378682/421432
+ if (mPresShell) {
+ mPresShell->LoadComplete();
+ }
+ }
+ }
+
+ nsJSContext::LoadEnd();
+
+#ifdef NS_PRINTING
+ // Check to see if someone tried to print during the load
+ if (mPrintIsPending) {
+ mPrintIsPending = false;
+ mPrintDocIsFullyLoaded = true;
+ Print(mCachedPrintSettings, mCachedPrintWebProgressListner);
+ mCachedPrintSettings = nullptr;
+ mCachedPrintWebProgressListner = nullptr;
+ }
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetLoadCompleted(bool *aOutLoadCompleted)
+{
+ *aOutLoadCompleted = mLoaded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PermitUnload(bool *aPermitUnload)
+{
+ bool shouldPrompt = true;
+ return PermitUnloadInternal(&shouldPrompt, aPermitUnload);
+}
+
+
+nsresult
+nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt,
+ bool *aPermitUnload)
+{
+ AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
+
+ nsresult rv = NS_OK;
+ *aPermitUnload = true;
+
+ if (!mDocument
+ || mInPermitUnload
+ || mInPermitUnloadPrompt) {
+ return NS_OK;
+ }
+
+ static bool sIsBeforeUnloadDisabled;
+ static bool sBeforeUnloadRequiresInteraction;
+ static bool sBeforeUnloadPrefsCached = false;
+
+ if (!sBeforeUnloadPrefsCached) {
+ sBeforeUnloadPrefsCached = true;
+ Preferences::AddBoolVarCache(&sIsBeforeUnloadDisabled,
+ BEFOREUNLOAD_DISABLED_PREFNAME);
+ Preferences::AddBoolVarCache(&sBeforeUnloadRequiresInteraction,
+ BEFOREUNLOAD_REQUIRES_INTERACTION_PREFNAME);
+ }
+
+ // First, get the script global object from the document...
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+
+ if (!window) {
+ // This is odd, but not fatal
+ NS_WARNING("window not set for document!");
+ return NS_OK;
+ }
+
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "This is unsafe");
+
+ // Now, fire an BeforeUnload event to the document and see if it's ok
+ // to unload...
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("beforeunloadevent"),
+ getter_AddRefs(event));
+ nsCOMPtr<nsIDOMBeforeUnloadEvent> beforeUnload = do_QueryInterface(event);
+ NS_ENSURE_STATE(beforeUnload);
+ event->InitEvent(NS_LITERAL_STRING("beforeunload"), false, true);
+
+ // Dispatching to |window|, but using |document| as the target.
+ event->SetTarget(mDocument);
+ event->SetTrusted(true);
+
+ // In evil cases we might be destroyed while handling the
+ // onbeforeunload event, don't let that happen. (see also bug#331040)
+ RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
+
+ bool dialogsAreEnabled = false;
+ {
+ // Never permit popups from the beforeunload handler, no matter
+ // how we get here.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ // Never permit dialogs from the beforeunload handler
+ nsGlobalWindow* globalWindow = nsGlobalWindow::Cast(window);
+ dialogsAreEnabled = globalWindow->AreDialogsEnabled();
+ nsGlobalWindow::TemporarilyDisableDialogs disableDialogs(globalWindow);
+
+ nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
+
+ mInPermitUnload = true;
+ {
+ Telemetry::AutoTimer<Telemetry::HANDLE_BEFOREUNLOAD_MS> telemetryTimer;
+ EventDispatcher::DispatchDOMEvent(window, nullptr, event, mPresContext,
+ nullptr);
+ }
+ mInPermitUnload = false;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ nsAutoString text;
+ beforeUnload->GetReturnValue(text);
+
+ // NB: we nullcheck mDocument because it might now be dead as a result of
+ // the event being dispatched.
+ if (!sIsBeforeUnloadDisabled && *aShouldPrompt && dialogsAreEnabled &&
+ mDocument && !(mDocument->GetSandboxFlags() & SANDBOXED_MODALS) &&
+ (!sBeforeUnloadRequiresInteraction || mDocument->UserHasInteracted()) &&
+ (event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) {
+ // Ask the user if it's ok to unload the current page
+
+ nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
+
+ if (prompt) {
+ nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt);
+ if (promptBag) {
+ bool isTabModalPromptAllowed;
+ GetIsTabModalPromptAllowed(&isTabModalPromptAllowed);
+ promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"),
+ isTabModalPromptAllowed);
+ }
+
+ nsXPIDLString title, message, stayLabel, leaveLabel;
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "OnBeforeUnloadTitle",
+ title);
+ nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "OnBeforeUnloadMessage",
+ message);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "OnBeforeUnloadLeaveButton",
+ leaveLabel);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "OnBeforeUnloadStayButton",
+ stayLabel);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ if (NS_FAILED(rv) || !title || !message || !stayLabel || !leaveLabel) {
+ NS_ERROR("Failed to get strings from dom.properties!");
+ return NS_OK;
+ }
+
+ // Although the exact value is ignored, we must not pass invalid
+ // bool values through XPConnect.
+ bool dummy = false;
+ int32_t buttonPressed = 0;
+ uint32_t buttonFlags = (nsIPrompt::BUTTON_POS_0_DEFAULT |
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) |
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_1));
+
+ nsAutoSyncOperation sync(mDocument);
+ mInPermitUnloadPrompt = true;
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_COUNT, 1);
+ rv = prompt->ConfirmEx(title, message, buttonFlags,
+ leaveLabel, stayLabel, nullptr, nullptr,
+ &dummy, &buttonPressed);
+ mInPermitUnloadPrompt = false;
+
+ // If the prompt aborted, we tell our consumer that it is not allowed
+ // to unload the page. One reason that prompts abort is that the user
+ // performed some action that caused the page to unload while our prompt
+ // was active. In those cases we don't want our consumer to also unload
+ // the page.
+ //
+ // XXX: Are there other cases where prompts can abort? Is it ok to
+ // prevent unloading the page in those cases?
+ if (NS_FAILED(rv)) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_ACTION, 2);
+ *aPermitUnload = false;
+ return NS_OK;
+ }
+
+ // Button 0 == leave, button 1 == stay
+ *aPermitUnload = (buttonPressed == 0);
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_ACTION,
+ (*aPermitUnload ? 1 : 0));
+ // If the user decided to go ahead, make sure not to prompt the user again
+ // by toggling the internal prompting bool to false:
+ if (*aPermitUnload) {
+ *aShouldPrompt = false;
+ }
+ }
+ }
+
+ if (docShell) {
+ int32_t childCount;
+ docShell->GetChildCount(&childCount);
+
+ for (int32_t i = 0; i < childCount && *aPermitUnload; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ docShell->GetChildAt(i, getter_AddRefs(item));
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
+
+ if (docShell) {
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+
+ if (cv) {
+ cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent)
+{
+ *aInEvent = mInPermitUnload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetInPermitUnload(bool* aInEvent)
+{
+ *aInEvent = mInPermitUnloadPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PageHide(bool aIsUnload)
+{
+ AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
+
+ mHidden = true;
+
+ if (!mDocument) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mDocument->OnPageHide(!aIsUnload, nullptr);
+
+ // inform the window so that the focus state is reset.
+ NS_ENSURE_STATE(mDocument);
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+ if (window)
+ window->PageHidden();
+
+ if (aIsUnload) {
+ // Poke the GC. The window might be collectable garbage now.
+ nsJSContext::PokeGC(JS::gcreason::PAGE_HIDE, NS_GC_DELAY * 2);
+
+ // if Destroy() was called during OnPageHide(), mDocument is nullptr.
+ NS_ENSURE_STATE(mDocument);
+
+ // First, get the window from the document...
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+
+ if (!window) {
+ // Fail if no window is available...
+ NS_WARNING("window not set for document!");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Now, fire an Unload event to the document...
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, eUnload);
+ event.mFlags.mBubbles = false;
+ // XXX Dispatching to |window|, but using |document| as the target.
+ event.mTarget = mDocument;
+
+ // Never permit popups from the unload handler, no matter how we get
+ // here.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
+
+ {
+ Telemetry::AutoTimer<Telemetry::HANDLE_UNLOAD_MS> telemetryTimer;
+ EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+ }
+ }
+
+#ifdef MOZ_XUL
+ // look for open menupopups and close them after the unload event, in case
+ // the unload event listeners open any new popups
+ nsContentUtils::HidePopupsInDocument(mDocument);
+#endif
+
+ return NS_OK;
+}
+
+static void
+AttachContainerRecurse(nsIDocShell* aShell)
+{
+ nsCOMPtr<nsIContentViewer> viewer;
+ aShell->GetContentViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ viewer->SetIsHidden(false);
+ nsIDocument* doc = viewer->GetDocument();
+ if (doc) {
+ doc->SetContainer(static_cast<nsDocShell*>(aShell));
+ }
+ RefPtr<nsPresContext> pc;
+ viewer->GetPresContext(getter_AddRefs(pc));
+ if (pc) {
+ pc->SetContainer(static_cast<nsDocShell*>(aShell));
+ nsCOMPtr<nsILinkHandler> handler = do_QueryInterface(aShell);
+ pc->SetLinkHandler(handler);
+ }
+ nsCOMPtr<nsIPresShell> presShell;
+ viewer->GetPresShell(getter_AddRefs(presShell));
+ if (presShell) {
+ presShell->SetForwardingContainer(WeakPtr<nsDocShell>());
+ }
+ }
+
+ // Now recurse through the children
+ int32_t childCount;
+ aShell->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ aShell->GetChildAt(i, getter_AddRefs(childItem));
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem);
+ AttachContainerRecurse(shell);
+ }
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Open(nsISupports *aState, nsISHEntry *aSHEntry)
+{
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+
+ if (mDocument)
+ mDocument->SetContainer(mContainer);
+
+ nsresult rv = InitInternal(mParentWidget, aState, mBounds, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHidden = false;
+
+ if (mPresShell)
+ mPresShell->SetForwardingContainer(WeakPtr<nsDocShell>());
+
+ // Rehook the child presentations. The child shells are still in
+ // session history, so get them from there.
+
+ if (aSHEntry) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ int32_t itemIndex = 0;
+ while (NS_SUCCEEDED(aSHEntry->ChildShellAt(itemIndex++,
+ getter_AddRefs(item))) && item) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
+ AttachContainerRecurse(shell);
+ }
+ }
+
+ SyncParentSubDocMap();
+
+ if (mFocusListener && mDocument) {
+ mDocument->AddEventListener(NS_LITERAL_STRING("focus"), mFocusListener,
+ false, false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("blur"), mFocusListener,
+ false, false);
+ }
+
+ // XXX re-enable image animations once that works correctly
+
+ PrepareToStartLoad();
+
+ // When loading a page from the bfcache with puppet widgets, we do the
+ // widget attachment here (it is otherwise done in MakeWindow, which is
+ // called for non-bfcache pages in the history, but not bfcache pages).
+ // Attachment is necessary, since we get detached when another page
+ // is browsed to. That is, if we are one page A, then when we go to
+ // page B, we detach. So page A's view has no widget. If we then go
+ // back to it, and it is in the bfcache, we will use that view, which
+ // doesn't have a widget. The attach call here will properly attach us.
+ if (nsIWidget::UsePuppetWidgets() && mPresContext &&
+ ShouldAttachToTopLevel()) {
+ // If the old view is already attached to our parent, detach
+ DetachFromTopLevelWidget();
+
+ nsViewManager *vm = GetViewManager();
+ MOZ_ASSERT(vm, "no view manager");
+ nsView* v = vm->GetRootView();
+ MOZ_ASSERT(v, "no root view");
+ MOZ_ASSERT(mParentWidget, "no mParentWidget to set");
+ v->AttachToTopLevelWidget(mParentWidget);
+
+ mAttachedToParent = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Close(nsISHEntry *aSHEntry)
+{
+ // All callers are supposed to call close to break circular
+ // references. If we do this stuff in the destructor, the
+ // destructor might never be called (especially if we're being
+ // used from JS.
+
+ mSHEntry = aSHEntry;
+
+ // Close is also needed to disable scripts during paint suppression,
+ // since we transfer the existing global object to the new document
+ // that is loaded. In the future, the global object may become a proxy
+ // for an object that can be switched in and out so that we don't need
+ // to disable scripts during paint suppression.
+
+ if (!mDocument)
+ return NS_OK;
+
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ // Turn scripting back on
+ // after PrintPreview had turned it off
+ if (GetIsPrintPreview() && mPrintEngine) {
+ mPrintEngine->TurnScriptingOn(true);
+ }
+#endif
+
+#ifdef NS_PRINTING
+ // A Close was called while we were printing
+ // so don't clear the ScriptGlobalObject
+ // or clear the mDocument below
+ if (mPrintEngine && !mClosingWhilePrinting) {
+ mClosingWhilePrinting = true;
+ } else
+#endif
+ {
+ // out of band cleanup of docshell
+ mDocument->SetScriptGlobalObject(nullptr);
+
+ if (!mSHEntry && mDocument)
+ mDocument->RemovedFromDocShell();
+ }
+
+ if (mFocusListener) {
+ mFocusListener->Disconnect();
+ if (mDocument) {
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"), mFocusListener,
+ false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"), mFocusListener,
+ false);
+ }
+ }
+
+ return NS_OK;
+}
+
+static void
+DetachContainerRecurse(nsIDocShell *aShell)
+{
+ // Unhook this docshell's presentation
+ nsCOMPtr<nsIContentViewer> viewer;
+ aShell->GetContentViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ nsIDocument* doc = viewer->GetDocument();
+ if (doc) {
+ doc->SetContainer(nullptr);
+ }
+ RefPtr<nsPresContext> pc;
+ viewer->GetPresContext(getter_AddRefs(pc));
+ if (pc) {
+ pc->Detach();
+ }
+ nsCOMPtr<nsIPresShell> presShell;
+ viewer->GetPresShell(getter_AddRefs(presShell));
+ if (presShell) {
+ auto weakShell = static_cast<nsDocShell*>(aShell);
+ presShell->SetForwardingContainer(weakShell);
+ }
+ }
+
+ // Now recurse through the children
+ int32_t childCount;
+ aShell->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ aShell->GetChildAt(i, getter_AddRefs(childItem));
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem);
+ DetachContainerRecurse(shell);
+ }
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Destroy()
+{
+ NS_ASSERTION(mDocument, "No document in Destroy()!");
+
+#ifdef NS_PRINTING
+ // Here is where we check to see if the document was still being prepared
+ // for printing when it was asked to be destroy from someone externally
+ // This usually happens if the document is unloaded while the user is in the
+ // Print Dialog
+ //
+ // So we flip the bool to remember that the document is going away
+ // and we can clean up and abort later after returning from the Print Dialog
+ if (mPrintEngine) {
+ if (mPrintEngine->CheckBeforeDestroy()) {
+ return NS_OK;
+ }
+ }
+ // Dispatch the 'afterprint' event now, if pending:
+ mAutoBeforeAndAfterPrint = nullptr;
+#endif
+
+ // Don't let the document get unloaded while we are printing.
+ // this could happen if we hit the back button during printing.
+ // We also keep the viewer from being cached in session history, since
+ // we require all documents there to be sanitized.
+ if (mDestroyRefCount != 0) {
+ --mDestroyRefCount;
+ return NS_OK;
+ }
+
+ // If we were told to put ourselves into session history instead of destroy
+ // the presentation, do that now.
+ if (mSHEntry) {
+ if (mPresShell)
+ mPresShell->Freeze();
+
+ // Make sure the presentation isn't torn down by Hide().
+ mSHEntry->SetSticky(mIsSticky);
+ mIsSticky = true;
+
+ bool savePresentation = mDocument ? mDocument->IsBFCachingAllowed() : true;
+
+ // Remove our root view from the view hierarchy.
+ if (mPresShell) {
+ nsViewManager *vm = mPresShell->GetViewManager();
+ if (vm) {
+ nsView *rootView = vm->GetRootView();
+
+ if (rootView) {
+ nsView *rootViewParent = rootView->GetParent();
+ if (rootViewParent) {
+ nsViewManager *parentVM = rootViewParent->GetViewManager();
+ if (parentVM) {
+ parentVM->RemoveChild(rootView);
+ }
+ }
+ }
+ }
+ }
+
+ Hide();
+
+ // This is after Hide() so that the user doesn't see the inputs clear.
+ if (mDocument) {
+ mDocument->Sanitize();
+ }
+
+ // Reverse ownership. Do this *after* calling sanitize so that sanitize
+ // doesn't cause mutations that make the SHEntry drop the presentation
+
+ // Grab a reference to mSHEntry before calling into things like
+ // SyncPresentationState that might mess with our members.
+ nsCOMPtr<nsISHEntry> shEntry = mSHEntry; // we'll need this below
+ mSHEntry = nullptr;
+
+ if (savePresentation) {
+ shEntry->SetContentViewer(this);
+ }
+
+ // Always sync the presentation state. That way even if someone screws up
+ // and shEntry has no window state at this point we'll be ok; we just won't
+ // cache ourselves.
+ shEntry->SyncPresentationState();
+
+ // Shut down accessibility for the document before we start to tear it down.
+#ifdef ACCESSIBILITY
+ if (mPresShell) {
+ a11y::DocAccessible* docAcc = mPresShell->GetDocAccessible();
+ if (docAcc) {
+ docAcc->Shutdown();
+ }
+ }
+#endif
+
+ // Break the link from the document/presentation to the docshell, so that
+ // link traversals cannot affect the currently-loaded document.
+ // When the presentation is restored, Open() and InitInternal() will reset
+ // these pointers to their original values.
+
+ if (mDocument) {
+ mDocument->SetContainer(nullptr);
+ }
+ if (mPresContext) {
+ mPresContext->Detach();
+ }
+ if (mPresShell) {
+ mPresShell->SetForwardingContainer(mContainer);
+ }
+
+ // Do the same for our children. Note that we need to get the child
+ // docshells from the SHEntry now; the docshell will have cleared them.
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ int32_t itemIndex = 0;
+ while (NS_SUCCEEDED(shEntry->ChildShellAt(itemIndex++,
+ getter_AddRefs(item))) && item) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
+ DetachContainerRecurse(shell);
+ }
+
+ return NS_OK;
+ }
+
+ // The document was not put in the bfcache
+
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+ if (mDocument) {
+ mDocument->Destroy();
+ mDocument = nullptr;
+ }
+
+ // All callers are supposed to call destroy to break circular
+ // references. If we do this stuff in the destructor, the
+ // destructor might never be called (especially if we're being
+ // used from JS.
+
+#ifdef NS_PRINTING
+ if (mPrintEngine) {
+ RefPtr<nsPrintEngine> printEngine = mozilla::Move(mPrintEngine);
+#ifdef NS_PRINT_PREVIEW
+ bool doingPrintPreview;
+ printEngine->GetDoingPrintPreview(&doingPrintPreview);
+ if (doingPrintPreview) {
+ printEngine->FinishPrintPreview();
+ }
+#endif
+ printEngine->Destroy();
+ MOZ_ASSERT(!mPrintEngine,
+ "mPrintEngine shouldn't be recreated while destroying it");
+ }
+#endif
+
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+
+ mDeviceContext = nullptr;
+
+ if (mPresContext) {
+ DestroyPresContext();
+ }
+
+ mWindow = nullptr;
+ mViewManager = nullptr;
+ mContainer = WeakPtr<nsDocShell>();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Stop(void)
+{
+ NS_ASSERTION(mDocument, "Stop called too early or too late");
+ if (mDocument) {
+ mDocument->StopDocumentLoad();
+ }
+
+ if (!mHidden && (mLoaded || mStopped) && mPresContext && !mSHEntry)
+ mPresContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
+
+ mStopped = true;
+
+ if (!mLoaded && mPresShell) {
+ // Well, we might as well paint what we have so far.
+ nsCOMPtr<nsIPresShell> shell(mPresShell); // bug 378682
+ shell->UnsuppressPainting();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetDOMDocument(nsIDOMDocument **aResult)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ return CallQueryInterface(mDocument, aResult);
+}
+
+NS_IMETHODIMP_(nsIDocument *)
+nsDocumentViewer::GetDocument()
+{
+ return mDocument;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetDOMDocument(nsIDOMDocument *aDocument)
+{
+ // Assumptions:
+ //
+ // 1) this document viewer has been initialized with a call to Init().
+ // 2) the stylesheets associated with the document have been added
+ // to the document.
+
+ // XXX Right now, this method assumes that the layout of the current
+ // document hasn't started yet. More cleanup will probably be
+ // necessary to make this method work for the case when layout *has*
+ // occurred for the current document.
+ // That work can happen when and if it is needed.
+
+ if (!aDocument)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIDocument> newDoc = do_QueryInterface(aDocument);
+ NS_ENSURE_TRUE(newDoc, NS_ERROR_UNEXPECTED);
+
+ return SetDocumentInternal(newDoc, false);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument,
+ bool aForceReuseInnerWindow)
+{
+ MOZ_ASSERT(aDocument);
+
+ // Set new container
+ aDocument->SetContainer(mContainer);
+
+ if (mDocument != aDocument) {
+ if (aForceReuseInnerWindow) {
+ // Transfer the navigation timing information to the new document, since
+ // we're keeping the same inner and hence should really have the same
+ // timing information.
+ aDocument->SetNavigationTiming(mDocument->GetNavigationTiming());
+ }
+
+ if (mDocument->IsStaticDocument()) {
+ mDocument->Destroy();
+ }
+
+ // Clear the list of old child docshells. Child docshells for the new
+ // document will be constructed as frames are created.
+ if (!aDocument->IsStaticDocument()) {
+ nsCOMPtr<nsIDocShell> node(mContainer);
+ if (node) {
+ int32_t count;
+ node->GetChildCount(&count);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ node->GetChildAt(0, getter_AddRefs(child));
+ node->RemoveChild(child);
+ }
+ }
+ }
+
+ // Replace the old document with the new one. Do this only when
+ // the new document really is a new document.
+ mDocument = aDocument;
+
+ // Set the script global object on the new document
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ mContainer ? mContainer->GetWindow() : nullptr;
+ if (window) {
+ nsresult rv = window->SetNewDocument(aDocument, nullptr,
+ aForceReuseInnerWindow);
+ if (NS_FAILED(rv)) {
+ Destroy();
+ return rv;
+ }
+ }
+ }
+
+ nsresult rv = SyncParentSubDocMap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Replace the current pres shell with a new shell for the new document
+
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ if (mPresContext) {
+ DestroyPresContext();
+
+ mWindow = nullptr;
+ rv = InitInternal(mParentWidget, nullptr, mBounds, true, true, false);
+ }
+
+ return rv;
+}
+
+nsIPresShell*
+nsDocumentViewer::GetPresShell()
+{
+ return mPresShell;
+}
+
+nsPresContext*
+nsDocumentViewer::GetPresContext()
+{
+ return mPresContext;
+}
+
+nsViewManager*
+nsDocumentViewer::GetViewManager()
+{
+ return mViewManager;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetPresShell(nsIPresShell** aResult)
+{
+ nsIPresShell* shell = GetPresShell();
+ NS_IF_ADDREF(*aResult = shell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetPresContext(nsPresContext** aResult)
+{
+ nsPresContext* pc = GetPresContext();
+ NS_IF_ADDREF(*aResult = pc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetBounds(nsIntRect& aResult)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ aResult = mBounds;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetPreviousViewer(nsIContentViewer** aViewer)
+{
+ *aViewer = mPreviousViewer;
+ NS_IF_ADDREF(*aViewer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetPreviousViewer(nsIContentViewer* aViewer)
+{
+ // NOTE: |Show| sets |mPreviousViewer| to null without calling this
+ // function.
+
+ if (aViewer) {
+ NS_ASSERTION(!mPreviousViewer,
+ "can't set previous viewer when there already is one");
+
+ // In a multiple chaining situation (which occurs when running a thrashing
+ // test like i-bench or jrgm's tests with no delay), we can build up a
+ // whole chain of viewers. In order to avoid this, we always set our previous
+ // viewer to the MOST previous viewer in the chain, and then dump the intermediate
+ // link from the chain. This ensures that at most only 2 documents are alive
+ // and undestroyed at any given time (the one that is showing and the one that
+ // is loading with painting suppressed).
+ // It's very important that if this ever gets changed the code
+ // before the RestorePresentation call in nsDocShell::InternalLoad
+ // be changed accordingly.
+ nsCOMPtr<nsIContentViewer> prevViewer;
+ aViewer->GetPreviousViewer(getter_AddRefs(prevViewer));
+ if (prevViewer) {
+ aViewer->SetPreviousViewer(nullptr);
+ aViewer->Destroy();
+ return SetPreviousViewer(prevViewer);
+ }
+ }
+
+ mPreviousViewer = aViewer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetBoundsWithFlags(const nsIntRect& aBounds, uint32_t aFlags)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ mBounds = aBounds;
+
+ if (mWindow && !mAttachedToParent) {
+ // Resize the widget, but don't trigger repaint. Layout will generate
+ // repaint requests during reflow.
+ mWindow->Resize(aBounds.x, aBounds.y,
+ aBounds.width, aBounds.height,
+ false);
+ } else if (mPresContext && mViewManager) {
+ // Ensure presContext's deviceContext is up to date, as we sometimes get
+ // here before a resolution-change notification has been fully handled
+ // during display configuration changes, especially when there are lots
+ // of windows/widgets competing to handle the notifications.
+ // (See bug 1154125.)
+ if (mPresContext->DeviceContext()->CheckDPIChange()) {
+ mPresContext->UIResolutionChanged();
+ }
+ int32_t p2a = mPresContext->AppUnitsPerDevPixel();
+ mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(mBounds.width, p2a),
+ NSIntPixelsToAppUnits(mBounds.height, p2a),
+ !!(aFlags & nsIContentViewer::eDelayResize));
+ }
+
+ // If there's a previous viewer, it's the one that's actually showing,
+ // so be sure to resize it as well so it paints over the right area.
+ // This may slow down the performance of the new page load, but resize
+ // during load is also probably a relatively unusual condition
+ // relating to things being hidden while something is loaded. It so
+ // happens that Firefox does this a good bit with its infobar, and it
+ // looks ugly if we don't do this.
+ if (mPreviousViewer) {
+ nsCOMPtr<nsIContentViewer> previousViewer = mPreviousViewer;
+ previousViewer->SetBounds(aBounds);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetBounds(const nsIntRect& aBounds)
+{
+ return SetBoundsWithFlags(aBounds, 0);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Move(int32_t aX, int32_t aY)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ mBounds.MoveTo(aX, aY);
+ if (mWindow) {
+ mWindow->Move(aX, aY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Show(void)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ // We don't need the previous viewer anymore since we're not
+ // displaying it.
+ if (mPreviousViewer) {
+ // This little dance *may* only be to keep
+ // PresShell::EndObservingDocument happy, but I'm not sure.
+ nsCOMPtr<nsIContentViewer> prevViewer(mPreviousViewer);
+ mPreviousViewer = nullptr;
+ prevViewer->Destroy();
+
+ // Make sure we don't have too many cached ContentViewers
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(mContainer);
+ if (treeItem) {
+ // We need to find the root DocShell since only that object has an
+ // SHistory and we need the SHistory to evict content viewers
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
+ nsCOMPtr<nsISHistory> history;
+ webNav->GetSessionHistory(getter_AddRefs(history));
+ nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
+ if (historyInt) {
+ int32_t prevIndex,loadedIndex;
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
+ docShell->GetPreviousTransIndex(&prevIndex);
+ docShell->GetLoadedTransIndex(&loadedIndex);
+#ifdef DEBUG_PAGE_CACHE
+ printf("About to evict content viewers: prev=%d, loaded=%d\n",
+ prevIndex, loadedIndex);
+#endif
+ historyInt->EvictOutOfRangeContentViewers(loadedIndex);
+ }
+ }
+ }
+
+ if (mWindow) {
+ // When attached to a top level xul window, we do not need to call
+ // Show on the widget. Underlying window management code handles
+ // this when the window is initialized.
+ if (!mAttachedToParent) {
+ mWindow->Show(true);
+ }
+ }
+
+ // Hold on to the document so we can use it after the script blocker below
+ // has been released (which might re-entrantly call into other
+ // nsDocumentViewer methods).
+ nsCOMPtr<nsIDocument> document = mDocument;
+
+ if (mDocument && !mPresShell) {
+ // The InitPresentationStuff call below requires a script blocker, because
+ // its PresShell::Initialize call can cause scripts to run and therefore
+ // re-entrant calls to nsDocumentViewer methods to be made.
+ nsAutoScriptBlocker scriptBlocker;
+
+ NS_ASSERTION(!mWindow, "Window already created but no presshell?");
+
+ nsCOMPtr<nsIBaseWindow> base_win(mContainer);
+ if (base_win) {
+ base_win->GetParentWidget(&mParentWidget);
+ if (mParentWidget) {
+ mParentWidget->Release(); // GetParentWidget AddRefs, but mParentWidget is weak
+ }
+ }
+
+ nsView* containerView = FindContainerView();
+
+ nsresult rv = CreateDeviceContext(containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create presentation context
+ NS_ASSERTION(!mPresContext, "Shouldn't have a prescontext if we have no shell!");
+ mPresContext = CreatePresContext(mDocument,
+ nsPresContext::eContext_Galley, containerView);
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = mPresContext->Init(mDeviceContext);
+ if (NS_FAILED(rv)) {
+ mPresContext = nullptr;
+ return rv;
+ }
+
+ rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width),
+ mPresContext->DevPixelsToAppUnits(mBounds.height)),
+ containerView);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mPresContext && base_win) {
+ nsCOMPtr<nsILinkHandler> linkHandler(do_GetInterface(base_win));
+
+ if (linkHandler) {
+ mPresContext->SetLinkHandler(linkHandler);
+ }
+
+ mPresContext->SetContainer(mContainer);
+ }
+
+ if (mPresContext) {
+ Hide();
+
+ rv = InitPresentationStuff(mDocument->MayStartLayout());
+ }
+
+ // If we get here the document load has already started and the
+ // window is shown because some JS on the page caused it to be
+ // shown...
+
+ if (mPresShell) {
+ nsCOMPtr<nsIPresShell> shell(mPresShell); // bug 378682
+ shell->UnsuppressPainting();
+ }
+ }
+
+ // Notify observers that a new page has been shown. This will get run
+ // from the event loop after we actually draw the page.
+ NS_DispatchToMainThread(new nsDocumentShownDispatcher(document));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Hide(void)
+{
+ if (!mAttachedToParent && mWindow) {
+ mWindow->Show(false);
+ }
+
+ if (!mPresShell)
+ return NS_OK;
+
+ NS_ASSERTION(mPresContext, "Can't have a presshell and no prescontext!");
+
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+
+ if (mIsSticky) {
+ // This window is sticky, that means that it might be shown again
+ // and we don't want the presshell n' all that to be thrown away
+ // just because the window is hidden.
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell) {
+#ifdef DEBUG
+ nsCOMPtr<nsIContentViewer> currentViewer;
+ docShell->GetContentViewer(getter_AddRefs(currentViewer));
+ MOZ_ASSERT(currentViewer == this);
+#endif
+ nsCOMPtr<nsILayoutHistoryState> layoutState;
+ mPresShell->CaptureHistoryState(getter_AddRefs(layoutState));
+ }
+
+ // Do not run ScriptRunners queued by DestroyPresShell() in the intermediate
+ // state before we're done destroying PresShell, PresContext, ViewManager, etc.
+ nsAutoScriptBlocker scriptBlocker;
+
+ DestroyPresShell();
+
+ DestroyPresContext();
+
+ mViewManager = nullptr;
+ mWindow = nullptr;
+ mDeviceContext = nullptr;
+ mParentWidget = nullptr;
+
+ nsCOMPtr<nsIBaseWindow> base_win(mContainer);
+
+ if (base_win && !mAttachedToParent) {
+ base_win->SetParentWidget(nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetSticky(bool *aSticky)
+{
+ *aSticky = mIsSticky;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetSticky(bool aSticky)
+{
+ mIsSticky = aSticky;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::RequestWindowClose(bool* aCanClose)
+{
+#ifdef NS_PRINTING
+ if (mPrintIsPending || (mPrintEngine && mPrintEngine->GetIsPrinting())) {
+ *aCanClose = false;
+ mDeferredWindowClose = true;
+ } else
+#endif
+ *aCanClose = true;
+
+ return NS_OK;
+}
+
+StyleSetHandle
+nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument)
+{
+ // Make sure this does the same thing as PresShell::AddSheet wrt ordering.
+
+ // this should eventually get expanded to allow for creating
+ // different sets for different media
+
+ StyleBackendType backendType = aDocument->GetStyleBackendType();
+
+ StyleSetHandle styleSet;
+ if (backendType == StyleBackendType::Gecko) {
+ styleSet = new nsStyleSet();
+ } else {
+ styleSet = new ServoStyleSet();
+ }
+
+ styleSet->BeginUpdate();
+
+ // The document will fill in the document sheets when we create the presshell
+
+ if (aDocument->IsBeingUsedAsImage()) {
+ MOZ_ASSERT(aDocument->IsSVGDocument(),
+ "Do we want to skip most sheets for this new image type?");
+
+ // SVG-as-an-image must be kept as light and small as possible. We
+ // deliberately skip loading everything and leave svg.css (and html.css and
+ // xul.css) to be loaded on-demand.
+ // XXXjwatt Nothing else is loaded on-demand, but I don't think that
+ // should matter for SVG-as-an-image. If it does, I want to know why!
+
+ // Caller will handle calling EndUpdate, per contract.
+ return styleSet;
+ }
+
+ auto cache = nsLayoutStylesheetCache::For(backendType);
+
+ // Handle the user sheets.
+ StyleSheet* sheet = nullptr;
+ if (nsContentUtils::IsInChromeDocshell(aDocument)) {
+ sheet = cache->UserChromeSheet();
+ } else {
+ sheet = cache->UserContentSheet();
+ }
+
+ if (sheet)
+ styleSet->AppendStyleSheet(SheetType::User, sheet);
+
+ // Append chrome sheets (scrollbars + forms).
+ bool shouldOverride = false;
+ // We don't want a docshell here for external resource docs, so just
+ // look at mContainer.
+ nsCOMPtr<nsIDocShell> ds(mContainer);
+ nsCOMPtr<nsIDOMEventTarget> chromeHandler;
+ nsCOMPtr<nsIURI> uri;
+ RefPtr<StyleSheet> chromeSheet;
+
+ if (ds) {
+ ds->GetChromeEventHandler(getter_AddRefs(chromeHandler));
+ }
+ if (chromeHandler) {
+ nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(chromeHandler));
+ nsCOMPtr<nsIContent> content(do_QueryInterface(elt));
+ if (elt && content) {
+ nsCOMPtr<nsIURI> baseURI = content->GetBaseURI();
+
+ nsAutoString sheets;
+ elt->GetAttribute(NS_LITERAL_STRING("usechromesheets"), sheets);
+ if (!sheets.IsEmpty() && baseURI) {
+ RefPtr<mozilla::css::Loader> cssLoader =
+ new mozilla::css::Loader(backendType);
+
+ char *str = ToNewCString(sheets);
+ char *newStr = str;
+ char *token;
+ while ( (token = nsCRT::strtok(newStr, ", ", &newStr)) ) {
+ NS_NewURI(getter_AddRefs(uri), nsDependentCString(token), nullptr,
+ baseURI);
+ if (!uri) continue;
+
+ cssLoader->LoadSheetSync(uri, &chromeSheet);
+ if (!chromeSheet) continue;
+
+ styleSet->PrependStyleSheet(SheetType::Agent, chromeSheet);
+ shouldOverride = true;
+ }
+ free(str);
+ }
+ }
+ }
+
+ if (!shouldOverride) {
+ sheet = cache->ScrollbarsSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+
+ if (!aDocument->IsSVGDocument()) {
+ // !!! IMPORTANT - KEEP THIS BLOCK IN SYNC WITH
+ // !!! SVGDocument::EnsureNonSVGUserAgentStyleSheetsLoaded.
+
+ // SVGForeignObjectElement::BindToTree calls SVGDocument::
+ // EnsureNonSVGUserAgentStyleSheetsLoaded to loads these UA sheet
+ // on-demand. (Excluding the quirks sheet, which should never be loaded for
+ // an SVG document, and excluding xul.css which will be loaded on demand by
+ // nsXULElement::BindToTree.)
+
+ sheet = cache->NumberControlSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+
+ sheet = cache->FormsSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+
+ if (aDocument->LoadsFullXULStyleSheetUpFront()) {
+ // nsXULElement::BindToTree loads xul.css on-demand if we don't load it
+ // up-front here.
+ sheet = cache->XULSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+
+ sheet = cache->MinimalXULSheet();
+ if (sheet) {
+ // Load the minimal XUL rules for scrollbars and a few other XUL things
+ // that non-XUL (typically HTML) documents commonly use.
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+
+ sheet = cache->CounterStylesSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+
+ if (nsLayoutUtils::ShouldUseNoScriptSheet(aDocument)) {
+ sheet = cache->NoScriptSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+
+ if (nsLayoutUtils::ShouldUseNoFramesSheet(aDocument)) {
+ sheet = cache->NoFramesSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+
+ // We don't add quirk.css here; nsPresContext::CompatibilityModeChanged will
+ // append it if needed.
+
+ sheet = cache->HTMLSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+
+ styleSet->PrependStyleSheet(SheetType::Agent,
+ cache->UASheet());
+ } else {
+ // SVG documents may have scrollbars and need the scrollbar styling.
+ sheet = cache->MinimalXULSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+
+ if (styleSet->IsGecko()) {
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ if (sheetService) {
+ for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
+ styleSet->AppendStyleSheet(SheetType::Agent, sheet);
+ }
+ for (StyleSheet* sheet : Reversed(*sheetService->UserStyleSheets())) {
+ styleSet->PrependStyleSheet(SheetType::User, sheet);
+ }
+ }
+ } else {
+ NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed "
+ "documents. See bug 1290224");
+ }
+
+ // Caller will handle calling EndUpdate, per contract.
+ return styleSet;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ClearHistoryEntry()
+{
+ mSHEntry = nullptr;
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+
+nsresult
+nsDocumentViewer::MakeWindow(const nsSize& aSize, nsView* aContainerView)
+{
+ if (GetIsPrintPreview())
+ return NS_OK;
+
+ bool shouldAttach = ShouldAttachToTopLevel();
+
+ if (shouldAttach) {
+ // If the old view is already attached to our parent, detach
+ DetachFromTopLevelWidget();
+ }
+
+ mViewManager = new nsViewManager();
+
+ nsDeviceContext *dx = mPresContext->DeviceContext();
+
+ nsresult rv = mViewManager->Init(dx);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The root view is always at 0,0.
+ nsRect tbounds(nsPoint(0, 0), aSize);
+ // Create a view
+ nsView* view = mViewManager->CreateView(tbounds, aContainerView);
+ if (!view)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Create a widget if we were given a parent widget or don't have a
+ // container view that we can hook up to without a widget.
+ // Don't create widgets for ResourceDocs (external resources & svg images),
+ // because when they're displayed, they're painted into *another* document's
+ // widget.
+ if (!mDocument->IsResourceDoc() &&
+ (mParentWidget || !aContainerView)) {
+ // pass in a native widget to be the parent widget ONLY if the view hierarchy will stand alone.
+ // otherwise the view will find its own parent widget and "do the right thing" to
+ // establish a parent/child widget relationship
+ nsWidgetInitData initData;
+ nsWidgetInitData* initDataPtr;
+ if (!mParentWidget) {
+ initDataPtr = &initData;
+ initData.mWindowType = eWindowType_invisible;
+ } else {
+ initDataPtr = nullptr;
+ }
+
+ if (shouldAttach) {
+ // Reuse the top level parent widget.
+ rv = view->AttachToTopLevelWidget(mParentWidget);
+ mAttachedToParent = true;
+ }
+ else if (!aContainerView && mParentWidget) {
+ rv = view->CreateWidgetForParent(mParentWidget, initDataPtr,
+ true, false);
+ }
+ else {
+ rv = view->CreateWidget(initDataPtr, true, false);
+ }
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Setup hierarchical relationship in view manager
+ mViewManager->SetRootView(view);
+
+ mWindow = view->GetWidget();
+
+ // This SetFocus is necessary so the Arrow Key and Page Key events
+ // go to the scrolled view as soon as the Window is created instead of going to
+ // the browser window (this enables keyboard scrolling of the document)
+ // mWindow->SetFocus();
+
+ return rv;
+}
+
+void
+nsDocumentViewer::DetachFromTopLevelWidget()
+{
+ if (mViewManager) {
+ nsView* oldView = mViewManager->GetRootView();
+ if (oldView && oldView->IsAttachedToTopLevel()) {
+ oldView->DetachFromTopLevelWidget();
+ }
+ }
+ mAttachedToParent = false;
+}
+
+nsView*
+nsDocumentViewer::FindContainerView()
+{
+ if (!mContainer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
+ if (!pwin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal();
+ if (!containerElement) {
+ return nullptr;
+ }
+
+ nsIFrame* subdocFrame = nsLayoutUtils::GetRealPrimaryFrameFor(containerElement);
+ if (!subdocFrame) {
+ // XXX Silenced by default in bug 1175289
+ LAYOUT_WARNING("Subdocument container has no frame");
+ return nullptr;
+ }
+
+ // subdocFrame might not be a subdocument frame; the frame
+ // constructor can treat a <frame> as an inline in some XBL
+ // cases. Treat that as display:none, the document is not
+ // displayed.
+ if (subdocFrame->GetType() != nsGkAtoms::subDocumentFrame) {
+ NS_WARNING_ASSERTION(!subdocFrame->GetType(),
+ "Subdocument container has non-subdocument frame");
+ return nullptr;
+ }
+
+ NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views");
+ return static_cast<nsSubDocumentFrame*>(subdocFrame)->EnsureInnerView();
+}
+
+nsresult
+nsDocumentViewer::CreateDeviceContext(nsView* aContainerView)
+{
+ NS_PRECONDITION(!mPresShell && !mWindow,
+ "This will screw up our existing presentation");
+ NS_PRECONDITION(mDocument, "Gotta have a document here");
+
+ nsIDocument* doc = mDocument->GetDisplayDocument();
+ if (doc) {
+ NS_ASSERTION(!aContainerView, "External resource document embedded somewhere?");
+ // We want to use our display document's device context if possible
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ nsPresContext* ctx = shell->GetPresContext();
+ if (ctx) {
+ mDeviceContext = ctx->DeviceContext();
+ return NS_OK;
+ }
+ }
+ }
+
+ // Create a device context even if we already have one, since our widget
+ // might have changed.
+ nsIWidget* widget = nullptr;
+ if (aContainerView) {
+ widget = aContainerView->GetNearestWidget(nullptr);
+ }
+ if (!widget) {
+ widget = mParentWidget;
+ }
+ if (widget) {
+ widget = widget->GetTopLevelWidget();
+ }
+
+ mDeviceContext = new nsDeviceContext();
+ mDeviceContext->Init(widget);
+ return NS_OK;
+}
+
+// Return the selection for the document. Note that text fields have their
+// own selection, which cannot be accessed with this method.
+mozilla::dom::Selection*
+nsDocumentViewer::GetDocumentSelection()
+{
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ return mPresShell->GetCurrentSelection(SelectionType::eNormal);
+}
+
+/* ========================================================================================
+ * nsIContentViewerEdit
+ * ======================================================================================== */
+
+NS_IMETHODIMP nsDocumentViewer::ClearSelection()
+{
+ // use nsCopySupport::GetSelectionForCopy() ?
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return selection->CollapseToStart();
+}
+
+NS_IMETHODIMP nsDocumentViewer::SelectAll()
+{
+ // XXX this is a temporary implementation copied from nsWebShell
+ // for now. I think nsDocument and friends should have some helper
+ // functions to make this easier.
+
+ // use nsCopySupport::GetSelectionForCopy() ?
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMHTMLDocument> htmldoc = do_QueryInterface(mDocument);
+ nsCOMPtr<nsIDOMNode> bodyNode;
+
+ nsresult rv;
+ if (htmldoc)
+ {
+ nsCOMPtr<nsIDOMHTMLElement>bodyElement;
+ rv = htmldoc->GetBody(getter_AddRefs(bodyElement));
+ if (NS_FAILED(rv) || !bodyElement) return rv;
+
+ bodyNode = do_QueryInterface(bodyElement);
+ }
+ else if (mDocument)
+ {
+ bodyNode = do_QueryInterface(mDocument->GetRootElement());
+ }
+ if (!bodyNode) return NS_ERROR_FAILURE;
+
+ rv = selection->RemoveAllRanges();
+ if (NS_FAILED(rv)) return rv;
+
+ mozilla::dom::Selection::AutoUserInitiated userSelection(selection);
+ rv = selection->SelectAllChildren(bodyNode);
+ return rv;
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopySelection()
+{
+ nsCopySupport::FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard,
+ mPresShell, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopyLinkLocation()
+{
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIDOMNode> node;
+ GetPopupLinkNode(getter_AddRefs(node));
+ // make noise if we're not in a link
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ nsCOMPtr<dom::Element> elm(do_QueryInterface(node));
+ NS_ENSURE_TRUE(elm, NS_ERROR_FAILURE);
+
+ nsAutoString locationText;
+ nsContentUtils::GetLinkLocation(elm, locationText);
+ if (locationText.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy the href onto the clipboard
+ return clipboard->CopyString(locationText);
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags)
+{
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIImageLoadingContent> node;
+ GetPopupImageNode(getter_AddRefs(node));
+ // make noise if we're not in an image
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContext> loadContext(mContainer);
+ return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags);
+}
+
+
+NS_IMETHODIMP nsDocumentViewer::GetCopyable(bool *aCopyable)
+{
+ NS_ENSURE_ARG_POINTER(aCopyable);
+ *aCopyable = nsCopySupport::CanCopy(mDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetContents(const char *mimeType, bool selectionOnly, nsAString& aOutValue)
+{
+ aOutValue.Truncate();
+
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ // Now we have the selection. Make sure it's nonzero:
+ nsCOMPtr<nsISelection> sel;
+ if (selectionOnly) {
+ nsCopySupport::GetSelectionForCopy(mDocument, getter_AddRefs(sel));
+ NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
+
+ bool isCollapsed;
+ sel->GetIsCollapsed(&isCollapsed);
+ if (isCollapsed)
+ return NS_OK;
+ }
+
+ // call the copy code
+ return nsCopySupport::GetContents(nsDependentCString(mimeType), 0, sel,
+ mDocument, aOutValue);
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetCanGetContents(bool *aCanGetContents)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetContents);
+ *aCanGetContents = false;
+ NS_ENSURE_STATE(mDocument);
+ *aCanGetContents = nsCopySupport::CanCopy(mDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::SetCommandNode(nsIDOMNode* aNode)
+{
+ nsIDocument* document = GetDocument();
+ NS_ENSURE_STATE(document);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
+ NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
+ NS_ENSURE_STATE(root);
+
+ root->SetPopupNode(aNode);
+ return NS_OK;
+}
+
+/* ========================================================================================
+ * nsIContentViewerFile
+ * ======================================================================================== */
+/** ---------------------------------------------------
+ * See documentation above in the nsIContentViewerfile class definition
+ * @update 01/24/00 dwc
+ */
+NS_IMETHODIMP
+nsDocumentViewer::Print(bool aSilent,
+ FILE * aDebugFile,
+ nsIPrintSettings* aPrintSettings)
+{
+#ifdef NS_PRINTING
+ nsCOMPtr<nsIPrintSettings> printSettings;
+
+#ifdef DEBUG
+ nsresult rv = NS_ERROR_FAILURE;
+
+ mDebugFile = aDebugFile;
+ // if they don't pass in a PrintSettings, then make one
+ // it will have all the default values
+ printSettings = aPrintSettings;
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc
+ = do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ // if they don't pass in a PrintSettings, then make one
+ if (printSettings == nullptr) {
+ printSettingsSvc->GetNewPrintSettings(getter_AddRefs(printSettings));
+ }
+ NS_ASSERTION(printSettings, "You can't PrintPreview without a PrintSettings!");
+ }
+ if (printSettings) printSettings->SetPrintSilent(aSilent);
+ if (printSettings) printSettings->SetShowPrintProgress(false);
+#endif
+
+
+ return Print(printSettings, nullptr);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+// nsIContentViewerFile interface
+NS_IMETHODIMP
+nsDocumentViewer::GetPrintable(bool *aPrintable)
+{
+ NS_ENSURE_ARG_POINTER(aPrintable);
+
+ *aPrintable = !GetIsPrinting();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::ScrollToNode(nsIDOMNode* aNode)
+{
+ NS_ENSURE_ARG(aNode);
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIPresShell> presShell;
+ NS_ENSURE_SUCCESS(GetPresShell(getter_AddRefs(presShell)), NS_ERROR_FAILURE);
+
+ // Get the nsIContent interface, because that's what we need to
+ // get the primary frame
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
+ NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
+
+ // Tell the PresShell to scroll to the primary frame of the content.
+ NS_ENSURE_SUCCESS(
+ presShell->ScrollContentIntoView(content,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_TOP,
+ nsIPresShell::SCROLL_ALWAYS),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN),
+ NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+void
+nsDocumentViewer::CallChildren(CallChildFunc aFunc, void* aClosure)
+{
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell)
+ {
+ int32_t i;
+ int32_t n;
+ docShell->GetChildCount(&n);
+ for (i=0; i < n; i++)
+ {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ docShell->GetChildAt(i, getter_AddRefs(child));
+ nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
+ NS_ASSERTION(childAsShell, "null child in docshell");
+ if (childAsShell)
+ {
+ nsCOMPtr<nsIContentViewer> childCV;
+ childAsShell->GetContentViewer(getter_AddRefs(childCV));
+ if (childCV)
+ {
+ (*aFunc)(childCV, aClosure);
+ }
+ }
+ }
+ }
+}
+
+static void
+ChangeChildPaintingEnabled(nsIContentViewer* aChild, void* aClosure)
+{
+ bool* enablePainting = (bool*) aClosure;
+ if (*enablePainting) {
+ aChild->ResumePainting();
+ } else {
+ aChild->PausePainting();
+ }
+}
+
+struct ZoomInfo
+{
+ float mZoom;
+};
+
+static void
+SetChildTextZoom(nsIContentViewer* aChild, void* aClosure)
+{
+ struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure;
+ aChild->SetTextZoom(ZoomInfo->mZoom);
+}
+
+static void
+SetChildMinFontSize(nsIContentViewer* aChild, void* aClosure)
+{
+ aChild->SetMinFontSize(NS_PTR_TO_INT32(aClosure));
+}
+
+static void
+SetChildFullZoom(nsIContentViewer* aChild, void* aClosure)
+{
+ struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure;
+ aChild->SetFullZoom(ZoomInfo->mZoom);
+}
+
+static void
+SetChildOverrideDPPX(nsIContentViewer* aChild, void* aClosure)
+{
+ struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure;
+ aChild->SetOverrideDPPX(ZoomInfo->mZoom);
+}
+
+static bool
+SetExtResourceTextZoom(nsIDocument* aDocument, void* aClosure)
+{
+ // Would it be better to enumerate external resource viewers instead?
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
+ ctxt->SetTextZoom(ZoomInfo->mZoom);
+ }
+ }
+
+ return true;
+}
+
+static bool
+SetExtResourceMinFontSize(nsIDocument* aDocument, void* aClosure)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ ctxt->SetBaseMinFontSize(NS_PTR_TO_INT32(aClosure));
+ }
+ }
+
+ return true;
+}
+
+static bool
+SetExtResourceFullZoom(nsIDocument* aDocument, void* aClosure)
+{
+ // Would it be better to enumerate external resource viewers instead?
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
+ ctxt->SetFullZoom(ZoomInfo->mZoom);
+ }
+ }
+
+ return true;
+}
+
+static bool
+SetExtResourceOverrideDPPX(nsIDocument* aDocument, void* aClosure)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
+ ctxt->SetOverrideDPPX(ZoomInfo->mZoom);
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetTextZoom(float aTextZoom)
+{
+ // If we don't have a document, then we need to bail.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (GetIsPrintPreview()) {
+ return NS_OK;
+ }
+
+ mTextZoom = aTextZoom;
+
+ // Set the text zoom on all children of mContainer (even if our zoom didn't
+ // change, our children's zoom may be different, though it would be unusual).
+ // Do this first, in case kids are auto-sizing and post reflow commands on
+ // our presshell (which should be subsumed into our own style change reflow).
+ struct ZoomInfo ZoomInfo = { aTextZoom };
+ CallChildren(SetChildTextZoom, &ZoomInfo);
+
+ // Now change our own zoom
+ nsPresContext* pc = GetPresContext();
+ if (pc && aTextZoom != mPresContext->TextZoom()) {
+ pc->SetTextZoom(aTextZoom);
+ }
+
+ // And do the external resources
+ mDocument->EnumerateExternalResources(SetExtResourceTextZoom, &ZoomInfo);
+
+ nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
+ NS_LITERAL_STRING("TextZoomChange"),
+ true, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetTextZoom(float* aTextZoom)
+{
+ NS_ENSURE_ARG_POINTER(aTextZoom);
+ nsPresContext* pc = GetPresContext();
+ *aTextZoom = pc ? pc->TextZoom() : 1.0f;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetMinFontSize(int32_t aMinFontSize)
+{
+ // If we don't have a document, then we need to bail.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (GetIsPrintPreview()) {
+ return NS_OK;
+ }
+
+ mMinFontSize = aMinFontSize;
+
+ // Set the min font on all children of mContainer (even if our min font didn't
+ // change, our children's min font may be different, though it would be unusual).
+ // Do this first, in case kids are auto-sizing and post reflow commands on
+ // our presshell (which should be subsumed into our own style change reflow).
+ CallChildren(SetChildMinFontSize, NS_INT32_TO_PTR(aMinFontSize));
+
+ // Now change our own min font
+ nsPresContext* pc = GetPresContext();
+ if (pc && aMinFontSize != mPresContext->MinFontSize(nullptr)) {
+ pc->SetBaseMinFontSize(aMinFontSize);
+ }
+
+ // And do the external resources
+ mDocument->EnumerateExternalResources(SetExtResourceMinFontSize,
+ NS_INT32_TO_PTR(aMinFontSize));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetMinFontSize(int32_t* aMinFontSize)
+{
+ NS_ENSURE_ARG_POINTER(aMinFontSize);
+ nsPresContext* pc = GetPresContext();
+ *aMinFontSize = pc ? pc->BaseMinFontSize() : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetFullZoom(float aFullZoom)
+{
+#ifdef NS_PRINT_PREVIEW
+ if (GetIsPrintPreview()) {
+ nsPresContext* pc = GetPresContext();
+ NS_ENSURE_TRUE(pc, NS_OK);
+ nsCOMPtr<nsIPresShell> shell = pc->GetPresShell();
+ NS_ENSURE_TRUE(shell, NS_OK);
+
+ if (!mPrintPreviewZoomed) {
+ mOriginalPrintPreviewScale = pc->GetPrintPreviewScale();
+ mPrintPreviewZoomed = true;
+ }
+
+ mPrintPreviewZoom = aFullZoom;
+ pc->SetPrintPreviewScale(aFullZoom * mOriginalPrintPreviewScale);
+ nsIPageSequenceFrame* pf = shell->GetPageSequenceFrame();
+ if (pf) {
+ nsIFrame* f = do_QueryFrame(pf);
+ shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ }
+
+ nsIFrame* rootFrame = shell->GetRootFrame();
+ if (rootFrame) {
+ rootFrame->InvalidateFrame();
+ }
+ return NS_OK;
+ }
+#endif
+
+ // If we don't have a document, then we need to bail.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool fullZoomChange = (mPageZoom != aFullZoom);
+ mPageZoom = aFullZoom;
+
+ struct ZoomInfo ZoomInfo = { aFullZoom };
+ CallChildren(SetChildFullZoom, &ZoomInfo);
+
+ nsPresContext* pc = GetPresContext();
+ if (pc) {
+ pc->SetFullZoom(aFullZoom);
+ }
+
+ // And do the external resources
+ mDocument->EnumerateExternalResources(SetExtResourceFullZoom, &ZoomInfo);
+
+ // Dispatch FullZoomChange event only if fullzoom value really was been changed
+ if (fullZoomChange) {
+ nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
+ NS_LITERAL_STRING("FullZoomChange"),
+ true, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetFullZoom(float* aFullZoom)
+{
+ NS_ENSURE_ARG_POINTER(aFullZoom);
+#ifdef NS_PRINT_PREVIEW
+ if (GetIsPrintPreview()) {
+ *aFullZoom = mPrintPreviewZoom;
+ return NS_OK;
+ }
+#endif
+ // Check the prescontext first because it might have a temporary
+ // setting for print-preview
+ nsPresContext* pc = GetPresContext();
+ *aFullZoom = pc ? pc->GetFullZoom() : mPageZoom;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetOverrideDPPX(float aDPPX)
+{
+ // If we don't have a document, then we need to bail.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mOverrideDPPX = aDPPX;
+
+ struct ZoomInfo ZoomInfo = { aDPPX };
+ CallChildren(SetChildOverrideDPPX, &ZoomInfo);
+
+ nsPresContext* pc = GetPresContext();
+ if (pc) {
+ pc->SetOverrideDPPX(aDPPX);
+ }
+
+ // And do the external resources
+ mDocument->EnumerateExternalResources(SetExtResourceOverrideDPPX, &ZoomInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetOverrideDPPX(float* aDPPX)
+{
+ NS_ENSURE_ARG_POINTER(aDPPX);
+
+ nsPresContext* pc = GetPresContext();
+ *aDPPX = pc ? pc->GetOverrideDPPX() : mOverrideDPPX;
+ return NS_OK;
+}
+
+static void
+SetChildAuthorStyleDisabled(nsIContentViewer* aChild, void* aClosure)
+{
+ bool styleDisabled = *static_cast<bool*>(aClosure);
+ aChild->SetAuthorStyleDisabled(styleDisabled);
+}
+
+
+NS_IMETHODIMP
+nsDocumentViewer::SetAuthorStyleDisabled(bool aStyleDisabled)
+{
+ if (mPresShell) {
+ mPresShell->SetAuthorStyleDisabled(aStyleDisabled);
+ }
+ CallChildren(SetChildAuthorStyleDisabled, &aStyleDisabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetAuthorStyleDisabled(bool* aStyleDisabled)
+{
+ if (mPresShell) {
+ *aStyleDisabled = mPresShell->GetAuthorStyleDisabled();
+ } else {
+ *aStyleDisabled = false;
+ }
+ return NS_OK;
+}
+
+static bool
+ExtResourceEmulateMedium(nsIDocument* aDocument, void* aClosure)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ const nsAString* mediaType = static_cast<nsAString*>(aClosure);
+ ctxt->EmulateMedium(*mediaType);
+ }
+ }
+
+ return true;
+}
+
+static void
+ChildEmulateMedium(nsIContentViewer* aChild, void* aClosure)
+{
+ const nsAString* mediaType = static_cast<nsAString*>(aClosure);
+ aChild->EmulateMedium(*mediaType);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::EmulateMedium(const nsAString& aMediaType)
+{
+ if (mPresContext) {
+ mPresContext->EmulateMedium(aMediaType);
+ }
+ CallChildren(ChildEmulateMedium, const_cast<nsAString*>(&aMediaType));
+
+ if (mDocument) {
+ mDocument->EnumerateExternalResources(ExtResourceEmulateMedium,
+ const_cast<nsAString*>(&aMediaType));
+ }
+
+ return NS_OK;
+}
+
+static bool
+ExtResourceStopEmulatingMedium(nsIDocument* aDocument, void* aClosure)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* ctxt = shell->GetPresContext();
+ if (ctxt) {
+ ctxt->StopEmulatingMedium();
+ }
+ }
+
+ return true;
+}
+
+static void
+ChildStopEmulatingMedium(nsIContentViewer* aChild, void* aClosure)
+{
+ aChild->StopEmulatingMedium();
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::StopEmulatingMedium()
+{
+ if (mPresContext) {
+ mPresContext->StopEmulatingMedium();
+ }
+ CallChildren(ChildStopEmulatingMedium, nullptr);
+
+ if (mDocument) {
+ mDocument->EnumerateExternalResources(ExtResourceStopEmulatingMedium,
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetForceCharacterSet(nsACString& aForceCharacterSet)
+{
+ aForceCharacterSet = mForceCharacterSet;
+ return NS_OK;
+}
+
+static void
+SetChildForceCharacterSet(nsIContentViewer* aChild, void* aClosure)
+{
+ const nsACString* charset = static_cast<nsACString*>(aClosure);
+ aChild->SetForceCharacterSet(*charset);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetForceCharacterSet(const nsACString& aForceCharacterSet)
+{
+ // This method is scriptable, so add-ons could pass in something other
+ // than a canonical name. However, in case where the input is a canonical
+ // name, "replacement" doesn't survive label resolution. Additionally, the
+ // empty string means no hint.
+ nsAutoCString encoding;
+ if (!aForceCharacterSet.IsEmpty()) {
+ if (aForceCharacterSet.EqualsLiteral("replacement")) {
+ encoding.AssignLiteral("replacement");
+ } else if (!EncodingUtils::FindEncodingForLabel(aForceCharacterSet,
+ encoding)) {
+ // Reject unknown labels
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ mForceCharacterSet = encoding;
+ // now set the force char set on all children of mContainer
+ CallChildren(SetChildForceCharacterSet, (void*) &aForceCharacterSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetHintCharacterSet(nsACString& aHintCharacterSet)
+{
+
+ if(kCharsetUninitialized == mHintCharsetSource) {
+ aHintCharacterSet.Truncate();
+ } else {
+ aHintCharacterSet = mHintCharset;
+ // this can't possibly be right. we can't set a value just because somebody got a related value!
+ //mHintCharsetSource = kCharsetUninitialized;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetHintCharacterSetSource(int32_t *aHintCharacterSetSource)
+{
+ NS_ENSURE_ARG_POINTER(aHintCharacterSetSource);
+
+ *aHintCharacterSetSource = mHintCharsetSource;
+ return NS_OK;
+}
+
+static void
+SetChildHintCharacterSetSource(nsIContentViewer* aChild, void* aClosure)
+{
+ aChild->SetHintCharacterSetSource(NS_PTR_TO_INT32(aClosure));
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetHintCharacterSetSource(int32_t aHintCharacterSetSource)
+{
+ mHintCharsetSource = aHintCharacterSetSource;
+ // now set the hint char set source on all children of mContainer
+ CallChildren(SetChildHintCharacterSetSource,
+ NS_INT32_TO_PTR(aHintCharacterSetSource));
+ return NS_OK;
+}
+
+static void
+SetChildHintCharacterSet(nsIContentViewer* aChild, void* aClosure)
+{
+ const nsACString* charset = static_cast<nsACString*>(aClosure);
+ aChild->SetHintCharacterSet(*charset);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetHintCharacterSet(const nsACString& aHintCharacterSet)
+{
+ // This method is scriptable, so add-ons could pass in something other
+ // than a canonical name. However, in case where the input is a canonical
+ // name, "replacement" doesn't survive label resolution. Additionally, the
+ // empty string means no hint.
+ nsAutoCString encoding;
+ if (!aHintCharacterSet.IsEmpty()) {
+ if (aHintCharacterSet.EqualsLiteral("replacement")) {
+ encoding.AssignLiteral("replacement");
+ } else if (!EncodingUtils::FindEncodingForLabel(aHintCharacterSet,
+ encoding)) {
+ // Reject unknown labels
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ mHintCharset = encoding;
+ // now set the hint char set on all children of mContainer
+ CallChildren(SetChildHintCharacterSet, (void*) &aHintCharacterSet);
+ return NS_OK;
+}
+
+static void
+AppendChildSubtree(nsIContentViewer* aChild, void* aClosure)
+{
+ nsTArray<nsCOMPtr<nsIContentViewer> >& array =
+ *static_cast<nsTArray<nsCOMPtr<nsIContentViewer> >*>(aClosure);
+ aChild->AppendSubtree(array);
+}
+
+NS_IMETHODIMP nsDocumentViewer::AppendSubtree(nsTArray<nsCOMPtr<nsIContentViewer> >& aArray)
+{
+ aArray.AppendElement(this);
+ CallChildren(AppendChildSubtree, &aArray);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PausePainting()
+{
+ bool enablePaint = false;
+ CallChildren(ChangeChildPaintingEnabled, &enablePaint);
+
+ nsIPresShell* presShell = GetPresShell();
+ if (presShell) {
+ presShell->PausePainting();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ResumePainting()
+{
+ bool enablePaint = true;
+ CallChildren(ChangeChildPaintingEnabled, &enablePaint);
+
+ nsIPresShell* presShell = GetPresShell();
+ if (presShell) {
+ presShell->ResumePainting();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDocumentViewer::GetContentSizeInternal(int32_t* aWidth, int32_t* aHeight,
+ nscoord aMaxWidth, nscoord aMaxHeight)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIPresShell> presShell;
+ GetPresShell(getter_AddRefs(presShell));
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // Flush out all content and style updates. We can't use a resize reflow
+ // because it won't change some sizes that a style change reflow will.
+ mDocument->FlushPendingNotifications(Flush_Layout);
+
+ nsIFrame *root = presShell->GetRootFrame();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ nscoord prefWidth;
+ {
+ nsRenderingContext rcx(presShell->CreateReferenceRenderingContext());
+ prefWidth = root->GetPrefISize(&rcx);
+ }
+ if (prefWidth > aMaxWidth) {
+ prefWidth = aMaxWidth;
+ }
+
+ nsresult rv = presShell->ResizeReflow(prefWidth, NS_UNCONSTRAINEDSIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsPresContext> presContext;
+ GetPresContext(getter_AddRefs(presContext));
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ // so how big is it?
+ nsRect shellArea = presContext->GetVisibleArea();
+ if (shellArea.height > aMaxHeight) {
+ // Reflow to max height if we would up too tall.
+ rv = presShell->ResizeReflow(prefWidth, aMaxHeight);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ shellArea = presContext->GetVisibleArea();
+ }
+
+ // Protect against bogus returns here
+ NS_ENSURE_TRUE(shellArea.width != NS_UNCONSTRAINEDSIZE &&
+ shellArea.height != NS_UNCONSTRAINEDSIZE,
+ NS_ERROR_FAILURE);
+
+ *aWidth = presContext->AppUnitsToDevPixels(shellArea.width);
+ *aHeight = presContext->AppUnitsToDevPixels(shellArea.height);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetContentSize(int32_t* aWidth, int32_t* aHeight)
+{
+ // Skip doing this on docshell-less documents for now
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(mContainer);
+ NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellParent;
+ docShellAsItem->GetSameTypeParent(getter_AddRefs(docShellParent));
+
+ // It's only valid to access this from a top frame. Doesn't work from
+ // sub-frames.
+ NS_ENSURE_TRUE(!docShellParent, NS_ERROR_FAILURE);
+
+ return GetContentSizeInternal(aWidth, aHeight, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetContentSizeConstrained(int32_t aMaxWidth, int32_t aMaxHeight,
+ int32_t* aWidth, int32_t* aHeight)
+{
+ RefPtr<nsPresContext> presContext;
+ GetPresContext(getter_AddRefs(presContext));
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ nscoord maxWidth = NS_UNCONSTRAINEDSIZE;
+ nscoord maxHeight = NS_UNCONSTRAINEDSIZE;
+ if (aMaxWidth > 0) {
+ maxWidth = presContext->DevPixelsToAppUnits(aMaxWidth);
+ }
+ if (aMaxHeight > 0) {
+ maxHeight = presContext->DevPixelsToAppUnits(aMaxHeight);
+ }
+
+ return GetContentSizeInternal(aWidth, aHeight, maxWidth, maxHeight);
+}
+
+
+NS_IMPL_ISUPPORTS(nsDocViewerSelectionListener, nsISelectionListener)
+
+nsresult nsDocViewerSelectionListener::Init(nsDocumentViewer *aDocViewer)
+{
+ mDocViewer = aDocViewer;
+ return NS_OK;
+}
+
+/*
+ * GetPopupNode, GetPopupLinkNode and GetPopupImageNode are helpers
+ * for the cmd_copyLink / cmd_copyImageLocation / cmd_copyImageContents family
+ * of commands. The focus controller stores the popup node, these retrieve
+ * them and munge appropriately. Note that we have to store the popup node
+ * rather than retrieving it from EventStateManager::GetFocusedContent because
+ * not all content (images included) can receive focus.
+ */
+
+nsresult
+nsDocumentViewer::GetPopupNode(nsIDOMNode** aNode)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ *aNode = nullptr;
+
+ // get the document
+ nsIDocument* document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ // get the private dom window
+ nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
+ NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
+ if (window) {
+ nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ // get the popup node
+ nsCOMPtr<nsIDOMNode> node = root->GetPopupNode();
+#ifdef MOZ_XUL
+ if (!node) {
+ nsPIDOMWindowOuter* rootWindow = root->GetWindow();
+ if (rootWindow) {
+ nsCOMPtr<nsIDocument> rootDoc = rootWindow->GetExtantDoc();
+ if (rootDoc) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ node = pm->GetLastTriggerPopupNode(rootDoc);
+ }
+ }
+ }
+ }
+#endif
+ node.swap(*aNode);
+ }
+
+ return NS_OK;
+}
+
+// GetPopupLinkNode: return popup link node or fail
+nsresult
+nsDocumentViewer::GetPopupLinkNode(nsIDOMNode** aNode)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ // you get null unless i say so
+ *aNode = nullptr;
+
+ // find popup node
+ nsCOMPtr<nsIDOMNode> node;
+ nsresult rv = GetPopupNode(getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // find out if we have a link in our ancestry
+ while (node) {
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ if (content) {
+ nsCOMPtr<nsIURI> hrefURI = content->GetHrefURI();
+ if (hrefURI) {
+ *aNode = node;
+ NS_IF_ADDREF(*aNode); // addref
+ return NS_OK;
+ }
+ }
+
+ // get our parent and keep trying...
+ nsCOMPtr<nsIDOMNode> parentNode;
+ node->GetParentNode(getter_AddRefs(parentNode));
+ node = parentNode;
+ }
+
+ // if we have no node, fail
+ return NS_ERROR_FAILURE;
+}
+
+// GetPopupLinkNode: return popup image node or fail
+nsresult
+nsDocumentViewer::GetPopupImageNode(nsIImageLoadingContent** aNode)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ // you get null unless i say so
+ *aNode = nullptr;
+
+ // find popup node
+ nsCOMPtr<nsIDOMNode> node;
+ nsresult rv = GetPopupNode(getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (node)
+ CallQueryInterface(node, aNode);
+
+ return NS_OK;
+}
+
+/*
+ * XXX dr
+ * ------
+ * These two functions -- GetInLink and GetInImage -- are kind of annoying
+ * in that they only get called from the controller (in
+ * nsDOMWindowController::IsCommandEnabled). The actual construction of the
+ * context menus in communicator (nsContextMenu.js) has its own, redundant
+ * tests. No big deal, but good to keep in mind if we ever clean context
+ * menus.
+ */
+
+NS_IMETHODIMP nsDocumentViewer::GetInLink(bool* aInLink)
+{
+#ifdef DEBUG_dr
+ printf("dr :: nsDocumentViewer::GetInLink\n");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aInLink);
+
+ // we're not in a link unless i say so
+ *aInLink = false;
+
+ // get the popup link
+ nsCOMPtr<nsIDOMNode> node;
+ nsresult rv = GetPopupLinkNode(getter_AddRefs(node));
+ if (NS_FAILED(rv)) return rv;
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ // if we made it here, we're in a link
+ *aInLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetInImage(bool* aInImage)
+{
+#ifdef DEBUG_dr
+ printf("dr :: nsDocumentViewer::GetInImage\n");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aInImage);
+
+ // we're not in an image unless i say so
+ *aInImage = false;
+
+ // get the popup image
+ nsCOMPtr<nsIImageLoadingContent> node;
+ nsresult rv = GetPopupImageNode(getter_AddRefs(node));
+ if (NS_FAILED(rv)) return rv;
+ if (!node) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure there is a URI assigned. This allows <input type="image"> to
+ // be an image but rejects other <input> types. This matches what
+ // nsContextMenu.js does.
+ nsCOMPtr<nsIURI> uri;
+ node->GetCurrentURI(getter_AddRefs(uri));
+ if (uri) {
+ // if we made it here, we're in an image
+ *aInImage = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocument *, nsISelection *, int16_t aReason)
+{
+ if (!mDocViewer) {
+ return NS_OK;
+ }
+
+ // get the selection state
+ RefPtr<mozilla::dom::Selection> selection = mDocViewer->GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocument* theDoc = mDocViewer->GetDocument();
+ if (!theDoc) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = theDoc->GetWindow();
+ if (!domWindow) return NS_ERROR_FAILURE;
+
+ bool selectionCollapsed;
+ selection->GetIsCollapsed(&selectionCollapsed);
+ // We only call UpdateCommands when the selection changes from collapsed to
+ // non-collapsed or vice versa, however we skip the initializing collapse. We
+ // might need another update string for simple selection changes, but that
+ // would be expenseive.
+ if (mSelectionWasCollapsed != selectionCollapsed)
+ {
+ domWindow->UpdateCommands(NS_LITERAL_STRING("select"), selection, aReason);
+ mSelectionWasCollapsed = selectionCollapsed;
+ }
+
+ return NS_OK;
+}
+
+//nsDocViewerFocusListener
+NS_IMPL_ISUPPORTS(nsDocViewerFocusListener,
+ nsIDOMEventListener)
+
+nsDocViewerFocusListener::nsDocViewerFocusListener()
+:mDocViewer(nullptr)
+{
+}
+
+nsDocViewerFocusListener::~nsDocViewerFocusListener(){}
+
+nsresult
+nsDocViewerFocusListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ NS_ENSURE_STATE(mDocViewer);
+
+ nsCOMPtr<nsIPresShell> shell;
+ mDocViewer->GetPresShell(getter_AddRefs(shell));
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(shell);
+ int16_t selectionStatus;
+ selCon->GetDisplaySelection(&selectionStatus);
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("focus")) {
+ // If selection was disabled, re-enable it.
+ if(selectionStatus == nsISelectionController::SELECTION_DISABLED ||
+ selectionStatus == nsISelectionController::SELECTION_HIDDEN) {
+ selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
+ selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
+ }
+ } else {
+ MOZ_ASSERT(eventType.EqualsLiteral("blur"), "Unexpected event type");
+ // If selection was on, disable it.
+ if(selectionStatus == nsISelectionController::SELECTION_ON ||
+ selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
+ selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
+ selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDocViewerFocusListener::Init(nsDocumentViewer *aDocViewer)
+{
+ mDocViewer = aDocViewer;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * From nsIWebBrowserPrint
+ */
+
+#ifdef NS_PRINTING
+
+NS_IMETHODIMP
+nsDocumentViewer::Print(nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener)
+{
+ // Printing XUL documents is not supported.
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
+ if (xulDoc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mContainer) {
+ PR_PL(("Container was destroyed yet we are still trying to use it!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ NS_ENSURE_STATE(docShell);
+
+ // Check to see if this document is still busy
+ // If it is busy and we aren't already "queued" up to print then
+ // Indicate there is a print pending and cache the args for later
+ uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
+ if ((NS_FAILED(docShell->GetBusyFlags(&busyFlags)) ||
+ (busyFlags != nsIDocShell::BUSY_FLAGS_NONE && busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) &&
+ !mPrintDocIsFullyLoaded) {
+ if (!mPrintIsPending) {
+ mCachedPrintSettings = aPrintSettings;
+ mCachedPrintWebProgressListner = aWebProgressListener;
+ mPrintIsPending = true;
+ }
+ PR_PL(("Printing Stopped - document is still busy!"));
+ return NS_ERROR_GFX_PRINTER_DOC_IS_BUSY;
+ }
+
+ if (!mDocument || !mDeviceContext) {
+ PR_PL(("Can't Print without a document and a device context"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // if we are printing another URL, then exit
+ // the reason we check here is because this method can be called while
+ // another is still in here (the printing dialog is a good example).
+ // the only time we can print more than one job at a time is the regression tests
+ if (GetIsPrinting()) {
+ // Let the user know we are not ready to print.
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (mPrintEngine) {
+ mPrintEngine->FirePrintingErrorEvent(rv);
+ }
+
+ return rv;
+ }
+
+ // Dispatch 'beforeprint' event and ensure 'afterprint' will be dispatched:
+ MOZ_ASSERT(!mAutoBeforeAndAfterPrint,
+ "We don't want to dispatch nested beforeprint/afterprint");
+ nsAutoPtr<AutoPrintEventDispatcher> autoBeforeAndAfterPrint(
+ new AutoPrintEventDispatcher(mDocument));
+ NS_ENSURE_STATE(!GetIsPrinting());
+ // If we are hosting a full-page plugin, tell it to print
+ // first. It shows its own native print UI.
+ nsCOMPtr<nsIPluginDocument> pDoc(do_QueryInterface(mDocument));
+ if (pDoc)
+ return pDoc->Print();
+
+ if (!mPrintEngine) {
+ NS_ENSURE_STATE(mDeviceContext);
+ mPrintEngine = new nsPrintEngine();
+
+ rv = mPrintEngine->Initialize(this, mContainer, mDocument,
+ float(mDeviceContext->AppUnitsPerCSSInch()) /
+ float(mDeviceContext->AppUnitsPerDevPixel()) /
+ mPageZoom,
+#ifdef DEBUG
+ mDebugFile
+#else
+ nullptr
+#endif
+ );
+ if (NS_FAILED(rv)) {
+ mPrintEngine->Destroy();
+ mPrintEngine = nullptr;
+ return rv;
+ }
+ }
+ if (mPrintEngine->HasPrintCallbackCanvas()) {
+ // Postpone the 'afterprint' event until after the mozPrintCallback
+ // callbacks have been called:
+ mAutoBeforeAndAfterPrint = autoBeforeAndAfterPrint;
+ }
+ dom::Element* root = mDocument->GetRootElement();
+ if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) {
+ mPrintEngine->SetDisallowSelectionPrint(true);
+ }
+ rv = mPrintEngine->Print(aPrintSettings, aWebProgressListener);
+ if (NS_FAILED(rv)) {
+ OnDonePrinting();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PrintPreview(nsIPrintSettings* aPrintSettings,
+ mozIDOMWindowProxy* aChildDOMWin,
+ nsIWebProgressListener* aWebProgressListener)
+{
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ NS_WARNING_ASSERTION(
+ IsInitializedForPrintPreview(),
+ "Using docshell.printPreview is the preferred way for print previewing!");
+
+ NS_ENSURE_ARG_POINTER(aChildDOMWin);
+ nsresult rv = NS_OK;
+
+ if (GetIsPrinting()) {
+ nsPrintEngine::CloseProgressDialog(aWebProgressListener);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Printing XUL documents is not supported.
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
+ if (xulDoc) {
+ nsPrintEngine::CloseProgressDialog(aWebProgressListener);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (!docShell || !mDeviceContext) {
+ PR_PL(("Can't Print Preview without device context and docshell"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aChildDOMWin);
+ MOZ_ASSERT(window);
+ nsCOMPtr<nsIDocument> doc = window->GetDoc();
+ NS_ENSURE_STATE(doc);
+
+ // Dispatch 'beforeprint' event and ensure 'afterprint' will be dispatched:
+ // XXX Currently[1] when the user switches between portrait and landscape
+ // mode in print preview, we re-enter this function before
+ // mAutoBeforeAndAfterPrint (if set) is cleared to dispatch the 'afterprint'
+ // event. To avoid sending multiple 'beforeprint'/'afterprint' events we
+ // must avoid creating a new AutoPrintEventDispatcher object here if we
+ // already have one saved in mAutoBeforeAndAfterPrint.
+ // [1] Until PDF.js is removed (though, maybe after that as well).
+ nsAutoPtr<AutoPrintEventDispatcher> autoBeforeAndAfterPrint;
+ if (!mAutoBeforeAndAfterPrint) {
+ autoBeforeAndAfterPrint = new AutoPrintEventDispatcher(doc);
+ }
+ NS_ENSURE_STATE(!GetIsPrinting());
+ // beforeprint event may have caused ContentViewer to be shutdown.
+ NS_ENSURE_STATE(mContainer);
+ NS_ENSURE_STATE(mDeviceContext);
+ if (!mPrintEngine) {
+ mPrintEngine = new nsPrintEngine();
+
+ rv = mPrintEngine->Initialize(this, mContainer, doc,
+ float(mDeviceContext->AppUnitsPerCSSInch()) /
+ float(mDeviceContext->AppUnitsPerDevPixel()) /
+ mPageZoom,
+#ifdef DEBUG
+ mDebugFile
+#else
+ nullptr
+#endif
+ );
+ if (NS_FAILED(rv)) {
+ mPrintEngine->Destroy();
+ mPrintEngine = nullptr;
+ return rv;
+ }
+ }
+ if (autoBeforeAndAfterPrint &&
+ mPrintEngine->HasPrintCallbackCanvas()) {
+ // Postpone the 'afterprint' event until after the mozPrintCallback
+ // callbacks have been called:
+ mAutoBeforeAndAfterPrint = autoBeforeAndAfterPrint;
+ }
+ dom::Element* root = doc->GetRootElement();
+ if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) {
+ PR_PL(("PrintPreview: found mozdisallowselectionprint"));
+ mPrintEngine->SetDisallowSelectionPrint(true);
+ }
+ rv = mPrintEngine->PrintPreview(aPrintSettings, aChildDOMWin, aWebProgressListener);
+ mPrintPreviewZoomed = false;
+ if (NS_FAILED(rv)) {
+ OnDonePrinting();
+ }
+ return rv;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+//----------------------------------------------------------------------
+NS_IMETHODIMP
+nsDocumentViewer::PrintPreviewNavigate(int16_t aType, int32_t aPageNum)
+{
+ if (!GetIsPrintPreview() ||
+ mPrintEngine->GetIsCreatingPrintPreview())
+ return NS_ERROR_FAILURE;
+
+ nsIScrollableFrame* sf =
+ mPrintEngine->GetPrintPreviewPresShell()->GetRootScrollFrameAsScrollable();
+ if (!sf)
+ return NS_OK;
+
+ // Check to see if we can short circut scrolling to the top
+ if (aType == nsIWebBrowserPrint::PRINTPREVIEW_HOME ||
+ (aType == nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM && aPageNum == 1)) {
+ sf->ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
+ return NS_OK;
+ }
+
+ // Finds the SimplePageSequencer frame
+ // in PP mPrtPreview->mPrintObject->mSeqFrame is null
+ nsIFrame* seqFrame = nullptr;
+ int32_t pageCount = 0;
+ if (NS_FAILED(mPrintEngine->GetSeqFrameAndCountPages(seqFrame, pageCount))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Figure where we are currently scrolled to
+ nsPoint pt = sf->GetScrollPosition();
+
+ int32_t pageNum = 1;
+ nsIFrame * fndPageFrame = nullptr;
+ nsIFrame * currentPage = nullptr;
+
+ // If it is "End" then just do a "goto" to the last page
+ if (aType == nsIWebBrowserPrint::PRINTPREVIEW_END) {
+ aType = nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM;
+ aPageNum = pageCount;
+ }
+
+ // Now, locate the current page we are on and
+ // and the page of the page number
+ for (nsIFrame* pageFrame : seqFrame->PrincipalChildList()) {
+ nsRect pageRect = pageFrame->GetRect();
+ if (pageRect.Contains(pageRect.x, pt.y)) {
+ currentPage = pageFrame;
+ }
+ if (pageNum == aPageNum) {
+ fndPageFrame = pageFrame;
+ break;
+ }
+ pageNum++;
+ }
+
+ if (aType == nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE) {
+ if (currentPage) {
+ fndPageFrame = currentPage->GetPrevInFlow();
+ if (!fndPageFrame) {
+ return NS_OK;
+ }
+ } else {
+ return NS_OK;
+ }
+ } else if (aType == nsIWebBrowserPrint::PRINTPREVIEW_NEXT_PAGE) {
+ if (currentPage) {
+ fndPageFrame = currentPage->GetNextInFlow();
+ if (!fndPageFrame) {
+ return NS_OK;
+ }
+ } else {
+ return NS_OK;
+ }
+ } else { // If we get here we are doing "GoTo"
+ if (aPageNum < 0 || aPageNum > pageCount) {
+ return NS_OK;
+ }
+ }
+
+ if (fndPageFrame) {
+ nscoord newYPosn =
+ nscoord(mPrintEngine->GetPrintPreviewScale() * fndPageFrame->GetPosition().y);
+ sf->ScrollTo(nsPoint(pt.x, newYPosn), nsIScrollableFrame::INSTANT);
+ }
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetGlobalPrintSettings(nsIPrintSettings * *aGlobalPrintSettings)
+{
+ return nsPrintEngine::GetGlobalPrintSettings(aGlobalPrintSettings);
+}
+
+// XXX This always returns false for subdocuments
+NS_IMETHODIMP
+nsDocumentViewer::GetDoingPrint(bool *aDoingPrint)
+{
+ NS_ENSURE_ARG_POINTER(aDoingPrint);
+
+ *aDoingPrint = false;
+ if (mPrintEngine) {
+ // XXX shouldn't this be GetDoingPrint() ?
+ return mPrintEngine->GetDoingPrintPreview(aDoingPrint);
+ }
+ return NS_OK;
+}
+
+// XXX This always returns false for subdocuments
+NS_IMETHODIMP
+nsDocumentViewer::GetDoingPrintPreview(bool *aDoingPrintPreview)
+{
+ NS_ENSURE_ARG_POINTER(aDoingPrintPreview);
+
+ *aDoingPrintPreview = false;
+ if (mPrintEngine) {
+ return mPrintEngine->GetDoingPrintPreview(aDoingPrintPreview);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetCurrentPrintSettings(nsIPrintSettings * *aCurrentPrintSettings)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentPrintSettings);
+
+ *aCurrentPrintSettings = nullptr;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetCurrentPrintSettings(aCurrentPrintSettings);
+}
+
+
+NS_IMETHODIMP
+nsDocumentViewer::GetCurrentChildDOMWindow(mozIDOMWindowProxy** aCurrentChildDOMWindow)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentChildDOMWindow);
+ *aCurrentChildDOMWindow = nullptr;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Cancel()
+{
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+ return mPrintEngine->Cancelled();
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ExitPrintPreview()
+{
+ if (GetIsPrinting())
+ return NS_ERROR_FAILURE;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ if (GetIsPrintPreview()) {
+ ReturnToGalleyPresentation();
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------------
+// Enumerate all the documents for their titles
+NS_IMETHODIMP
+nsDocumentViewer::EnumerateDocumentNames(uint32_t* aCount,
+ char16_t*** aResult)
+{
+#ifdef NS_PRINTING
+ NS_ENSURE_ARG(aCount);
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->EnumerateDocumentNames(aCount, aResult);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected)
+{
+#ifdef NS_PRINTING
+ *aIsFramesetFrameSelected = false;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetIsFramesetFrameSelected(aIsFramesetFrameSelected);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages)
+{
+#ifdef NS_PRINTING
+ NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages);
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetPrintPreviewNumPages(aPrintPreviewNumPages);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsFramesetDocument(bool *aIsFramesetDocument)
+{
+#ifdef NS_PRINTING
+ *aIsFramesetDocument = false;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetIsFramesetDocument(aIsFramesetDocument);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsIFrameSelected(bool *aIsIFrameSelected)
+{
+#ifdef NS_PRINTING
+ *aIsIFrameSelected = false;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetIsIFrameSelected(aIsIFrameSelected);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsRangeSelection(bool *aIsRangeSelection)
+{
+#ifdef NS_PRINTING
+ *aIsRangeSelection = false;
+ NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE);
+
+ return mPrintEngine->GetIsRangeSelection(aIsRangeSelection);
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+//----------------------------------------------------------------------------------
+// Printing/Print Preview Helpers
+//----------------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------------
+// Walks the document tree and tells each DocShell whether Printing/PP is happening
+void
+nsDocumentViewer::SetIsPrintingInDocShellTree(nsIDocShellTreeItem* aParentNode,
+ bool aIsPrintingOrPP,
+ bool aStartAtTop)
+{
+ nsCOMPtr<nsIDocShellTreeItem> parentItem(do_QueryInterface(aParentNode));
+
+ // find top of "same parent" tree
+ if (aStartAtTop) {
+ if (aIsPrintingOrPP) {
+ while (parentItem) {
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ parentItem->GetSameTypeParent(getter_AddRefs(parent));
+ if (!parent) {
+ break;
+ }
+ parentItem = do_QueryInterface(parent);
+ }
+ mTopContainerWhilePrinting = do_GetWeakReference(parentItem);
+ } else {
+ parentItem = do_QueryReferent(mTopContainerWhilePrinting);
+ }
+ }
+
+ // Check to see if the DocShell's ContentViewer is printing/PP
+ nsCOMPtr<nsIContentViewerContainer> viewerContainer(do_QueryInterface(parentItem));
+ if (viewerContainer) {
+ viewerContainer->SetIsPrinting(aIsPrintingOrPP);
+ }
+
+ if (!aParentNode) {
+ return;
+ }
+
+ // Traverse children to see if any of them are printing.
+ int32_t n;
+ aParentNode->GetChildCount(&n);
+ for (int32_t i=0; i < n; i++) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ aParentNode->GetChildAt(i, getter_AddRefs(child));
+ NS_ASSERTION(child, "child isn't nsIDocShell");
+ if (child) {
+ SetIsPrintingInDocShellTree(child, aIsPrintingOrPP, false);
+ }
+ }
+
+}
+#endif // NS_PRINTING
+
+bool
+nsDocumentViewer::ShouldAttachToTopLevel()
+{
+ if (!mParentWidget)
+ return false;
+
+ nsCOMPtr<nsIDocShellTreeItem> containerItem(mContainer);
+ if (!containerItem)
+ return false;
+
+ // We always attach when using puppet widgets
+ if (nsIWidget::UsePuppetWidgets())
+ return true;
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
+ // On windows, in the parent process we also attach, but just to
+ // chrome items
+ nsWindowType winType = mParentWidget->WindowType();
+ if ((winType == eWindowType_toplevel ||
+ winType == eWindowType_dialog ||
+ winType == eWindowType_invisible) &&
+ containerItem->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+//------------------------------------------------------------
+// XXX this always returns false for subdocuments
+bool
+nsDocumentViewer::GetIsPrinting()
+{
+#ifdef NS_PRINTING
+ if (mPrintEngine) {
+ return mPrintEngine->GetIsPrinting();
+ }
+#endif
+ return false;
+}
+
+//------------------------------------------------------------
+// Notification from the PrintEngine of the current Printing status
+void
+nsDocumentViewer::SetIsPrinting(bool aIsPrinting)
+{
+#ifdef NS_PRINTING
+ // Set all the docShells in the docshell tree to be printing.
+ // that way if anyone of them tries to "navigate" it can't
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell || !aIsPrinting) {
+ SetIsPrintingInDocShellTree(docShell, aIsPrinting, true);
+ } else {
+ NS_WARNING("Did you close a window before printing?");
+ }
+
+ if (!aIsPrinting) {
+ // Dispatch the 'afterprint' event now, if pending:
+ mAutoBeforeAndAfterPrint = nullptr;
+ }
+#endif
+}
+
+//------------------------------------------------------------
+// The PrintEngine holds the current value
+// this called from inside the DocViewer.
+// XXX it always returns false for subdocuments
+bool
+nsDocumentViewer::GetIsPrintPreview()
+{
+#ifdef NS_PRINTING
+ if (mPrintEngine) {
+ return mPrintEngine->GetIsPrintPreview();
+ }
+#endif
+ return false;
+}
+
+//------------------------------------------------------------
+// Notification from the PrintEngine of the current PP status
+void
+nsDocumentViewer::SetIsPrintPreview(bool aIsPrintPreview)
+{
+#ifdef NS_PRINTING
+ // Set all the docShells in the docshell tree to be printing.
+ // that way if anyone of them tries to "navigate" it can't
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell || !aIsPrintPreview) {
+ SetIsPrintingInDocShellTree(docShell, aIsPrintPreview, true);
+ }
+ if (!aIsPrintPreview) {
+ // Dispatch the 'afterprint' event now, if pending:
+ mAutoBeforeAndAfterPrint = nullptr;
+ }
+#endif
+
+ // Protect against pres shell destruction running scripts.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (!aIsPrintPreview) {
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+ mWindow = nullptr;
+ mViewManager = nullptr;
+ mPresContext = nullptr;
+ mPresShell = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------------------
+// nsIDocumentViewerPrint IFace
+//----------------------------------------------------------------------------------
+
+//------------------------------------------------------------
+void
+nsDocumentViewer::IncrementDestroyRefCount()
+{
+ ++mDestroyRefCount;
+}
+
+//------------------------------------------------------------
+
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+//------------------------------------------------------------
+// Reset ESM focus for all descendent doc shells.
+static void
+ResetFocusState(nsIDocShell* aDocShell)
+{
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm)
+ return;
+
+ nsCOMPtr<nsISimpleEnumerator> docShellEnumerator;
+ aDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
+ nsIDocShell::ENUMERATE_FORWARDS,
+ getter_AddRefs(docShellEnumerator));
+
+ nsCOMPtr<nsISupports> currentContainer;
+ bool hasMoreDocShells;
+ while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells))
+ && hasMoreDocShells) {
+ docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
+ nsCOMPtr<nsPIDOMWindowOuter> win = do_GetInterface(currentContainer);
+ if (win)
+ fm->ClearFocus(win);
+ }
+}
+#endif // NS_PRINTING && NS_PRINT_PREVIEW
+
+void
+nsDocumentViewer::ReturnToGalleyPresentation()
+{
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ if (!GetIsPrintPreview()) {
+ NS_ERROR("Wow, we should never get here!");
+ return;
+ }
+
+ SetIsPrintPreview(false);
+
+ mPrintEngine->TurnScriptingOn(true);
+ mPrintEngine->Destroy();
+ mPrintEngine = nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ ResetFocusState(docShell);
+
+ SetTextZoom(mTextZoom);
+ SetFullZoom(mPageZoom);
+ SetOverrideDPPX(mOverrideDPPX);
+ SetMinFontSize(mMinFontSize);
+ Show();
+
+#endif // NS_PRINTING && NS_PRINT_PREVIEW
+}
+
+//------------------------------------------------------------
+// This called ONLY when printing has completed and the DV
+// is being notified that it should get rid of the PrintEngine.
+//
+// BUT, if we are in Print Preview then we want to ignore the
+// notification (we do not get rid of the PrintEngine)
+//
+// One small caveat:
+// This IS called from two places in this module for cleaning
+// up when an error occurred during the start up printing
+// and print preview
+//
+void
+nsDocumentViewer::OnDonePrinting()
+{
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ if (mPrintEngine) {
+ RefPtr<nsPrintEngine> pe = mPrintEngine;
+ if (GetIsPrintPreview()) {
+ pe->DestroyPrintingData();
+ } else {
+ mPrintEngine = nullptr;
+ pe->Destroy();
+ }
+
+ // We are done printing, now cleanup
+ if (mDeferredWindowClose) {
+ mDeferredWindowClose = false;
+ if (mContainer) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(mContainer->GetWindow())) {
+ win->Close();
+ }
+ }
+ } else if (mClosingWhilePrinting) {
+ if (mDocument) {
+ mDocument->Destroy();
+ mDocument = nullptr;
+ }
+ mClosingWhilePrinting = false;
+ }
+ }
+#endif // NS_PRINTING && NS_PRINT_PREVIEW
+}
+
+NS_IMETHODIMP nsDocumentViewer::SetPageMode(bool aPageMode, nsIPrintSettings* aPrintSettings)
+{
+ // XXX Page mode is only partially working; it's currently used for
+ // reftests that require a paginated context
+ mIsPageMode = aPageMode;
+
+ // The DestroyPresShell call requires a script blocker, since the
+ // PresShell::Destroy call it does can cause scripts to run, which could
+ // re-entrantly call methods on the nsDocumentViewer.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ if (mPresContext) {
+ DestroyPresContext();
+ }
+
+ mViewManager = nullptr;
+ mWindow = nullptr;
+
+ NS_ENSURE_STATE(mDocument);
+ if (aPageMode)
+ {
+ mPresContext = CreatePresContext(mDocument,
+ nsPresContext::eContext_PageLayout, FindContainerView());
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+ mPresContext->SetPaginatedScrolling(true);
+ mPresContext->SetPrintSettings(aPrintSettings);
+ nsresult rv = mPresContext->Init(mDeviceContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_ENSURE_SUCCESS(InitInternal(mParentWidget, nullptr, mBounds, true, false),
+ NS_ERROR_FAILURE);
+
+ Show();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetHistoryEntry(nsISHEntry **aHistoryEntry)
+{
+ NS_IF_ADDREF(*aHistoryEntry = mSHEntry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsTabModalPromptAllowed(bool *aAllowed)
+{
+ *aAllowed = !mHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsHidden(bool *aHidden)
+{
+ *aHidden = mHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetIsHidden(bool aHidden)
+{
+ mHidden = aHidden;
+ return NS_OK;
+}
+
+void
+nsDocumentViewer::DestroyPresShell()
+{
+ // We assert this because destroying the pres shell could otherwise cause
+ // re-entrancy into nsDocumentViewer methods, and all callers of
+ // DestroyPresShell need to do other cleanup work afterwards before it
+ // is safe for those re-entrant method calls to be made.
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "DestroyPresShell must only be called when scripts are blocked");
+
+ // Break circular reference (or something)
+ mPresShell->EndObservingDocument();
+
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (selection && mSelectionListener)
+ selection->RemoveSelectionListener(mSelectionListener);
+
+ mPresShell->Destroy();
+ mPresShell = nullptr;
+}
+
+void
+nsDocumentViewer::DestroyPresContext()
+{
+ mPresContext->Detach();
+ mPresContext = nullptr;
+}
+
+bool
+nsDocumentViewer::IsInitializedForPrintPreview()
+{
+ return mInitializedForPrintPreview;
+}
+
+void
+nsDocumentViewer::InitializeForPrintPreview()
+{
+ mInitializedForPrintPreview = true;
+}
+
+void
+nsDocumentViewer::SetPrintPreviewPresentation(nsViewManager* aViewManager,
+ nsPresContext* aPresContext,
+ nsIPresShell* aPresShell)
+{
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ mWindow = nullptr;
+ mViewManager = aViewManager;
+ mPresContext = aPresContext;
+ mPresShell = aPresShell;
+
+ if (ShouldAttachToTopLevel()) {
+ DetachFromTopLevelWidget();
+ nsView* rootView = mViewManager->GetRootView();
+ rootView->AttachToTopLevelWidget(mParentWidget);
+ mAttachedToParent = true;
+ }
+}
+
+// Fires the "document-shown" event so that interested parties are aware of it.
+NS_IMETHODIMP
+nsDocumentShownDispatcher::Run()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(mDocument, "document-shown", nullptr);
+ }
+ return NS_OK;
+}
+
diff --git a/layout/base/nsFrameManager.cpp b/layout/base/nsFrameManager.cpp
new file mode 100644
index 000000000..97b022da8
--- /dev/null
+++ b/layout/base/nsFrameManager.cpp
@@ -0,0 +1,835 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:cindent:ts=2:et:sw=2:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* storage of the frame tree and information about it */
+
+#include "nscore.h"
+#include "nsIPresShell.h"
+#include "nsStyleContext.h"
+#include "nsCOMPtr.h"
+#include "plhash.h"
+#include "nsPlaceholderFrame.h"
+#include "nsGkAtoms.h"
+#include "nsILayoutHistoryState.h"
+#include "nsPresState.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocument.h"
+
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsAutoPtr.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "ChildIterator.h"
+
+#include "nsFrameManager.h"
+#include "GeckoProfiler.h"
+#include "nsIStatefulFrame.h"
+#include "nsContainerFrame.h"
+
+ #ifdef DEBUG
+ //#define DEBUG_UNDISPLAYED_MAP
+ //#define DEBUG_DISPLAY_CONTENTS_MAP
+ #else
+ #undef DEBUG_UNDISPLAYED_MAP
+ #undef DEBUG_DISPLAY_CONTENTS_MAP
+ #endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+
+struct PlaceholderMapEntry : public PLDHashEntryHdr {
+ // key (the out of flow frame) can be obtained through placeholder frame
+ nsPlaceholderFrame *placeholderFrame;
+};
+
+static bool
+PlaceholderMapMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ const PlaceholderMapEntry *entry =
+ static_cast<const PlaceholderMapEntry*>(hdr);
+ NS_ASSERTION(entry->placeholderFrame->GetOutOfFlowFrame() !=
+ (void*)0xdddddddd,
+ "Dead placeholder in placeholder map");
+ return entry->placeholderFrame->GetOutOfFlowFrame() == key;
+}
+
+static const PLDHashTableOps PlaceholderMapOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ PlaceholderMapMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+nsFrameManagerBase::nsFrameManagerBase()
+ : mPresShell(nullptr)
+ , mRootFrame(nullptr)
+ , mPlaceholderMap(&PlaceholderMapOps, sizeof(PlaceholderMapEntry))
+ , mUndisplayedMap(nullptr)
+ , mDisplayContentsMap(nullptr)
+ , mIsDestroyingFrames(false)
+{
+}
+
+//----------------------------------------------------------------------
+
+// XXXldb This seems too complicated for what I think it's doing, and it
+// should also be using PLDHashTable rather than plhash to use less memory.
+
+class nsFrameManagerBase::UndisplayedMap {
+public:
+ explicit UndisplayedMap(uint32_t aNumBuckets = 16);
+ ~UndisplayedMap(void);
+
+ UndisplayedNode* GetFirstNode(nsIContent* aParentContent);
+
+ nsresult AddNodeFor(nsIContent* aParentContent,
+ nsIContent* aChild, nsStyleContext* aStyle);
+
+ void RemoveNodeFor(nsIContent* aParentContent,
+ UndisplayedNode* aNode);
+
+ void RemoveNodesFor(nsIContent* aParentContent);
+ UndisplayedNode* UnlinkNodesFor(nsIContent* aParentContent);
+
+ // Removes all entries from the hash table
+ void Clear(void);
+
+protected:
+ /**
+ * Gets the entry for the provided parent content. If the content
+ * is a <xbl:children> element, |**aParentContent| is set to
+ * the parent of the children element.
+ */
+ PLHashEntry** GetEntryFor(nsIContent** aParentContent);
+ void AppendNodeFor(UndisplayedNode* aNode,
+ nsIContent* aParentContent);
+
+ PLHashTable* mTable;
+ PLHashEntry** mLastLookup;
+};
+
+//----------------------------------------------------------------------
+
+nsFrameManager::~nsFrameManager()
+{
+ NS_ASSERTION(!mPresShell, "nsFrameManager::Destroy never called");
+}
+
+void
+nsFrameManager::Destroy()
+{
+ NS_ASSERTION(mPresShell, "Frame manager already shut down.");
+
+ // Destroy the frame hierarchy.
+ mPresShell->SetIgnoreFrameDestruction(true);
+
+ // Unregister all placeholders before tearing down the frame tree
+ nsFrameManager::ClearPlaceholderFrameMap();
+
+ if (mRootFrame) {
+ mRootFrame->Destroy();
+ mRootFrame = nullptr;
+ }
+
+ delete mUndisplayedMap;
+ mUndisplayedMap = nullptr;
+ delete mDisplayContentsMap;
+ mDisplayContentsMap = nullptr;
+
+ mPresShell = nullptr;
+}
+
+//----------------------------------------------------------------------
+
+// Placeholder frame functions
+nsPlaceholderFrame*
+nsFrameManager::GetPlaceholderFrameFor(const nsIFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame, "null param unexpected");
+
+ auto entry = static_cast<PlaceholderMapEntry*>
+ (const_cast<PLDHashTable*>(&mPlaceholderMap)->Search(aFrame));
+ if (entry) {
+ return entry->placeholderFrame;
+ }
+
+ return nullptr;
+}
+
+void
+nsFrameManager::RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame)
+{
+ MOZ_ASSERT(aPlaceholderFrame, "null param unexpected");
+ MOZ_ASSERT(nsGkAtoms::placeholderFrame == aPlaceholderFrame->GetType(),
+ "unexpected frame type");
+ auto entry = static_cast<PlaceholderMapEntry*>
+ (mPlaceholderMap.Add(aPlaceholderFrame->GetOutOfFlowFrame()));
+ MOZ_ASSERT(!entry->placeholderFrame,
+ "Registering a placeholder for a frame that already has a placeholder!");
+ entry->placeholderFrame = aPlaceholderFrame;
+}
+
+void
+nsFrameManager::UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame)
+{
+ NS_PRECONDITION(aPlaceholderFrame, "null param unexpected");
+ NS_PRECONDITION(nsGkAtoms::placeholderFrame == aPlaceholderFrame->GetType(),
+ "unexpected frame type");
+
+ mPlaceholderMap.Remove(aPlaceholderFrame->GetOutOfFlowFrame());
+}
+
+void
+nsFrameManager::ClearPlaceholderFrameMap()
+{
+ for (auto iter = mPlaceholderMap.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PlaceholderMapEntry*>(iter.Get());
+ entry->placeholderFrame->SetOutOfFlowFrame(nullptr);
+ }
+ mPlaceholderMap.Clear();
+}
+
+//----------------------------------------------------------------------
+
+/* static */ nsStyleContext*
+nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent)
+{
+ if (!aContent) {
+ return nullptr;
+ }
+ nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
+ MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+ for (UndisplayedNode* node = aMap->GetFirstNode(parent);
+ node; node = node->mNext) {
+ if (node->mContent == aContent)
+ return node->mStyle;
+ }
+
+ return nullptr;
+}
+
+/* static */ UndisplayedNode*
+nsFrameManager::GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
+ nsIContent* aParentContent)
+{
+ return aMap ? aMap->GetFirstNode(aParentContent) : nullptr;
+}
+
+UndisplayedNode*
+nsFrameManager::GetAllUndisplayedContentIn(nsIContent* aParentContent)
+{
+ return GetAllUndisplayedNodesInMapFor(mUndisplayedMap, aParentContent);
+}
+
+/* static */ void
+nsFrameManager::SetStyleContextInMap(UndisplayedMap* aMap,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ NS_PRECONDITION(!aStyleContext->GetPseudo(),
+ "Should only have actual elements here");
+
+#if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
+ static int i = 0;
+ printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
+#endif
+
+ NS_ASSERTION(!GetStyleContextInMap(aMap, aContent),
+ "Already have an entry for aContent");
+
+ nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
+ MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+#ifdef DEBUG
+ nsIPresShell* shell = aStyleContext->PresContext()->PresShell();
+ NS_ASSERTION(parent || (shell && shell->GetDocument() &&
+ shell->GetDocument()->GetRootElement() == aContent),
+ "undisplayed content must have a parent, unless it's the root "
+ "element");
+#endif
+ aMap->AddNodeFor(parent, aContent, aStyleContext);
+}
+
+void
+nsFrameManager::SetUndisplayedContent(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (!mUndisplayedMap) {
+ mUndisplayedMap = new UndisplayedMap;
+ }
+ SetStyleContextInMap(mUndisplayedMap, aContent, aStyleContext);
+}
+
+/* static */ void
+nsFrameManager::ChangeStyleContextInMap(UndisplayedMap* aMap,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ MOZ_ASSERT(aMap, "expecting a map");
+
+#if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
+ static int i = 0;
+ printf("ChangeStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
+#endif
+
+ for (UndisplayedNode* node = aMap->GetFirstNode(aContent->GetParent());
+ node; node = node->mNext) {
+ if (node->mContent == aContent) {
+ node->mStyle = aStyleContext;
+ return;
+ }
+ }
+
+ MOZ_CRASH("couldn't find the entry to change");
+}
+
+void
+nsFrameManager::ClearUndisplayedContentIn(nsIContent* aContent,
+ nsIContent* aParentContent)
+{
+#ifdef DEBUG_UNDISPLAYED_MAP
+ static int i = 0;
+ printf("ClearUndisplayedContent(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent);
+#endif
+
+ if (mUndisplayedMap) {
+ UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent);
+ while (node) {
+ if (node->mContent == aContent) {
+ mUndisplayedMap->RemoveNodeFor(aParentContent, node);
+
+#ifdef DEBUG_UNDISPLAYED_MAP
+ printf( "REMOVED!\n");
+#endif
+#ifdef DEBUG
+ // make sure that there are no more entries for the same content
+ nsStyleContext *context = GetUndisplayedContent(aContent);
+ NS_ASSERTION(context == nullptr, "Found more undisplayed content data after removal");
+#endif
+ return;
+ }
+ node = node->mNext;
+ }
+ }
+#ifdef DEBUG_UNDISPLAYED_MAP
+ printf( "not found.\n");
+#endif
+}
+
+void
+nsFrameManager::ClearAllUndisplayedContentIn(nsIContent* aParentContent)
+{
+#ifdef DEBUG_UNDISPLAYED_MAP
+ static int i = 0;
+ printf("ClearAllUndisplayedContentIn(%d): parent=%p \n", i++, (void*)aParentContent);
+#endif
+
+ if (mUndisplayedMap) {
+ mUndisplayedMap->RemoveNodesFor(aParentContent);
+ }
+
+ // Need to look at aParentContent's content list due to XBL insertions.
+ // Nodes in aParentContent's content list do not have aParentContent as a
+ // parent, but are treated as children of aParentContent. We iterate over
+ // the flattened content list and just ignore any nodes we don't care about.
+ FlattenedChildIterator iter(aParentContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->GetParent() != aParentContent) {
+ ClearUndisplayedContentIn(child, child->GetParent());
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+void
+nsFrameManager::SetDisplayContents(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (!mDisplayContentsMap) {
+ mDisplayContentsMap = new UndisplayedMap;
+ }
+ SetStyleContextInMap(mDisplayContentsMap, aContent, aStyleContext);
+}
+
+UndisplayedNode*
+nsFrameManager::GetAllDisplayContentsIn(nsIContent* aParentContent)
+{
+ return GetAllUndisplayedNodesInMapFor(mDisplayContentsMap, aParentContent);
+}
+
+void
+nsFrameManager::ClearDisplayContentsIn(nsIContent* aContent,
+ nsIContent* aParentContent)
+{
+#ifdef DEBUG_DISPLAY_CONTENTS_MAP
+ static int i = 0;
+ printf("ClearDisplayContents(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent);
+#endif
+
+ if (mDisplayContentsMap) {
+ UndisplayedNode* node = mDisplayContentsMap->GetFirstNode(aParentContent);
+ while (node) {
+ if (node->mContent == aContent) {
+ mDisplayContentsMap->RemoveNodeFor(aParentContent, node);
+
+#ifdef DEBUG_DISPLAY_CONTENTS_MAP
+ printf( "REMOVED!\n");
+#endif
+#ifdef DEBUG
+ // make sure that there are no more entries for the same content
+ nsStyleContext* context = GetDisplayContentsStyleFor(aContent);
+ NS_ASSERTION(context == nullptr, "Found more entries for aContent after removal");
+#endif
+ ClearAllDisplayContentsIn(aContent);
+ ClearAllUndisplayedContentIn(aContent);
+ return;
+ }
+ node = node->mNext;
+ }
+ }
+#ifdef DEBUG_DISPLAY_CONTENTS_MAP
+ printf( "not found.\n");
+#endif
+}
+
+void
+nsFrameManager::ClearAllDisplayContentsIn(nsIContent* aParentContent)
+{
+#ifdef DEBUG_DISPLAY_CONTENTS_MAP
+ static int i = 0;
+ printf("ClearAllDisplayContentsIn(%d): parent=%p \n", i++, (void*)aParentContent);
+#endif
+
+ if (mDisplayContentsMap) {
+ UndisplayedNode* cur = mDisplayContentsMap->UnlinkNodesFor(aParentContent);
+ while (cur) {
+ UndisplayedNode* next = cur->mNext;
+ cur->mNext = nullptr;
+ ClearAllDisplayContentsIn(cur->mContent);
+ ClearAllUndisplayedContentIn(cur->mContent);
+ delete cur;
+ cur = next;
+ }
+ }
+
+ // Need to look at aParentContent's content list due to XBL insertions.
+ // Nodes in aParentContent's content list do not have aParentContent as a
+ // parent, but are treated as children of aParentContent. We iterate over
+ // the flattened content list and just ignore any nodes we don't care about.
+ FlattenedChildIterator iter(aParentContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->GetParent() != aParentContent) {
+ ClearDisplayContentsIn(child, child->GetParent());
+ ClearUndisplayedContentIn(child, child->GetParent());
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+void
+nsFrameManager::AppendFrames(nsContainerFrame* aParentFrame,
+ ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ if (aParentFrame->IsAbsoluteContainer() &&
+ aListID == aParentFrame->GetAbsoluteListID()) {
+ aParentFrame->GetAbsoluteContainingBlock()->
+ AppendFrames(aParentFrame, aListID, aFrameList);
+ } else {
+ aParentFrame->AppendFrames(aListID, aFrameList);
+ }
+}
+
+void
+nsFrameManager::InsertFrames(nsContainerFrame* aParentFrame,
+ ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_PRECONDITION(!aPrevFrame || (!aPrevFrame->GetNextContinuation()
+ || (((aPrevFrame->GetNextContinuation()->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER))
+ && !(aPrevFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER))),
+ "aPrevFrame must be the last continuation in its chain!");
+
+ if (aParentFrame->IsAbsoluteContainer() &&
+ aListID == aParentFrame->GetAbsoluteListID()) {
+ aParentFrame->GetAbsoluteContainingBlock()->
+ InsertFrames(aParentFrame, aListID, aPrevFrame, aFrameList);
+ } else {
+ aParentFrame->InsertFrames(aListID, aPrevFrame, aFrameList);
+ }
+}
+
+void
+nsFrameManager::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ bool wasDestroyingFrames = mIsDestroyingFrames;
+ mIsDestroyingFrames = true;
+
+ // In case the reflow doesn't invalidate anything since it just leaves
+ // a gap where the old frame was, we invalidate it here. (This is
+ // reasonably likely to happen when removing a last child in a way
+ // that doesn't change the size of the parent.)
+ // This has to sure to invalidate the entire overflow rect; this
+ // is important in the presence of absolute positioning
+ aOldFrame->InvalidateFrameForRemoval();
+
+ NS_ASSERTION(!aOldFrame->GetPrevContinuation() ||
+ // exception for nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames
+ aOldFrame->GetType() == nsGkAtoms::textFrame,
+ "Must remove first continuation.");
+ NS_ASSERTION(!(aOldFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW &&
+ GetPlaceholderFrameFor(aOldFrame)),
+ "Must call RemoveFrame on placeholder for out-of-flows.");
+ nsContainerFrame* parentFrame = aOldFrame->GetParent();
+ if (parentFrame->IsAbsoluteContainer() &&
+ aListID == parentFrame->GetAbsoluteListID()) {
+ parentFrame->GetAbsoluteContainingBlock()->
+ RemoveFrame(parentFrame, aListID, aOldFrame);
+ } else {
+ parentFrame->RemoveFrame(aListID, aOldFrame);
+ }
+
+ mIsDestroyingFrames = wasDestroyingFrames;
+}
+
+//----------------------------------------------------------------------
+
+void
+nsFrameManager::NotifyDestroyingFrame(nsIFrame* aFrame)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->GetPrimaryFrame() == aFrame) {
+ ClearAllUndisplayedContentIn(content);
+ ClearAllDisplayContentsIn(content);
+ }
+}
+
+// Capture state for a given frame.
+// Accept a content id here, in some cases we may not have content (scroll position)
+void
+nsFrameManager::CaptureFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState)
+{
+ if (!aFrame || !aState) {
+ NS_WARNING("null frame, or state");
+ return;
+ }
+
+ // Only capture state for stateful frames
+ nsIStatefulFrame* statefulFrame = do_QueryFrame(aFrame);
+ if (!statefulFrame) {
+ return;
+ }
+
+ // Capture the state, exit early if we get null (nothing to save)
+ nsAutoPtr<nsPresState> frameState;
+ nsresult rv = statefulFrame->SaveState(getter_Transfers(frameState));
+ if (!frameState) {
+ return;
+ }
+
+ // Generate the hash key to store the state under
+ // Exit early if we get empty key
+ nsAutoCString stateKey;
+ nsIContent* content = aFrame->GetContent();
+ nsIDocument* doc = content ? content->GetUncomposedDoc() : nullptr;
+ rv = statefulFrame->GenerateStateKey(content, doc, stateKey);
+ if(NS_FAILED(rv) || stateKey.IsEmpty()) {
+ return;
+ }
+
+ // Store the state. aState owns frameState now.
+ aState->AddState(stateKey, frameState.forget());
+}
+
+void
+nsFrameManager::CaptureFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState)
+{
+ NS_PRECONDITION(nullptr != aFrame && nullptr != aState, "null parameters passed in");
+
+ CaptureFrameStateFor(aFrame, aState);
+
+ // Now capture state recursively for the frame hierarchy rooted at aFrame
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* child = childFrames.get();
+ if (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ // We'll pick it up when we get to its placeholder
+ continue;
+ }
+ // Make sure to walk through placeholders as needed, so that we
+ // save state for out-of-flows which may not be our descendants
+ // themselves but whose placeholders are our descendants.
+ CaptureFrameState(nsPlaceholderFrame::GetRealFrameFor(child), aState);
+ }
+ }
+}
+
+// Restore state for a given frame.
+// Accept a content id here, in some cases we may not have content (scroll position)
+void
+nsFrameManager::RestoreFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState)
+{
+ if (!aFrame || !aState) {
+ NS_WARNING("null frame or state");
+ return;
+ }
+
+ // Only restore state for stateful frames
+ nsIStatefulFrame* statefulFrame = do_QueryFrame(aFrame);
+ if (!statefulFrame) {
+ return;
+ }
+
+ // Generate the hash key the state was stored under
+ // Exit early if we get empty key
+ nsIContent* content = aFrame->GetContent();
+ // If we don't have content, we can't generate a hash
+ // key and there's probably no state information for us.
+ if (!content) {
+ return;
+ }
+
+ nsAutoCString stateKey;
+ nsIDocument* doc = content->GetUncomposedDoc();
+ nsresult rv = statefulFrame->GenerateStateKey(content, doc, stateKey);
+ if (NS_FAILED(rv) || stateKey.IsEmpty()) {
+ return;
+ }
+
+ // Get the state from the hash
+ nsPresState* frameState = aState->GetState(stateKey);
+ if (!frameState) {
+ return;
+ }
+
+ // Restore it
+ rv = statefulFrame->RestoreState(frameState);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // If we restore ok, remove the state from the state table
+ aState->RemoveState(stateKey);
+}
+
+void
+nsFrameManager::RestoreFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState)
+{
+ NS_PRECONDITION(nullptr != aFrame && nullptr != aState, "null parameters passed in");
+
+ RestoreFrameStateFor(aFrame, aState);
+
+ // Now restore state recursively for the frame hierarchy rooted at aFrame
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ RestoreFrameState(childFrames.get(), aState);
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+static PLHashNumber
+HashKey(void* key)
+{
+ return NS_PTR_TO_INT32(key);
+}
+
+static int
+CompareKeys(void* key1, void* key2)
+{
+ return key1 == key2;
+}
+
+//----------------------------------------------------------------------
+
+nsFrameManagerBase::UndisplayedMap::UndisplayedMap(uint32_t aNumBuckets)
+{
+ MOZ_COUNT_CTOR(nsFrameManagerBase::UndisplayedMap);
+ mTable = PL_NewHashTable(aNumBuckets, (PLHashFunction)HashKey,
+ (PLHashComparator)CompareKeys,
+ (PLHashComparator)nullptr,
+ nullptr, nullptr);
+ mLastLookup = nullptr;
+}
+
+nsFrameManagerBase::UndisplayedMap::~UndisplayedMap(void)
+{
+ MOZ_COUNT_DTOR(nsFrameManagerBase::UndisplayedMap);
+ Clear();
+ PL_HashTableDestroy(mTable);
+}
+
+PLHashEntry**
+nsFrameManagerBase::UndisplayedMap::GetEntryFor(nsIContent** aParentContent)
+{
+ nsIContent* parentContent = *aParentContent;
+
+ if (mLastLookup && (parentContent == (*mLastLookup)->key)) {
+ return mLastLookup;
+ }
+
+ // In the case of XBL default content, <xbl:children> elements do not get a
+ // frame causing a mismatch between the content tree and the frame tree.
+ // |GetEntryFor| is sometimes called with the content tree parent (which may
+ // be a <xbl:children> element) but the parent in the frame tree would be the
+ // insertion parent (parent of the <xbl:children> element). Here the children
+ // elements are normalized to the insertion parent to correct for the mismatch.
+ if (parentContent && nsContentUtils::IsContentInsertionPoint(parentContent)) {
+ parentContent = parentContent->GetParent();
+ // Change the caller's pointer for the parent content to be the insertion parent.
+ *aParentContent = parentContent;
+ }
+
+ PLHashNumber hashCode = NS_PTR_TO_INT32(parentContent);
+ PLHashEntry** entry = PL_HashTableRawLookup(mTable, hashCode, parentContent);
+ if (*entry) {
+ mLastLookup = entry;
+ }
+ return entry;
+}
+
+UndisplayedNode*
+nsFrameManagerBase::UndisplayedMap::GetFirstNode(nsIContent* aParentContent)
+{
+ PLHashEntry** entry = GetEntryFor(&aParentContent);
+ if (*entry) {
+ return (UndisplayedNode*)((*entry)->value);
+ }
+ return nullptr;
+}
+
+void
+nsFrameManagerBase::UndisplayedMap::AppendNodeFor(UndisplayedNode* aNode,
+ nsIContent* aParentContent)
+{
+ PLHashEntry** entry = GetEntryFor(&aParentContent);
+ if (*entry) {
+ UndisplayedNode* node = (UndisplayedNode*)((*entry)->value);
+ while (node->mNext) {
+ if (node->mContent == aNode->mContent) {
+ // We actually need to check this in optimized builds because
+ // there are some callers that do this. See bug 118014, bug
+ // 136704, etc.
+ NS_NOTREACHED("node in map twice");
+ delete aNode;
+ return;
+ }
+ node = node->mNext;
+ }
+ node->mNext = aNode;
+ }
+ else {
+ PLHashNumber hashCode = NS_PTR_TO_INT32(aParentContent);
+ PL_HashTableRawAdd(mTable, entry, hashCode, aParentContent, aNode);
+ mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
+ }
+}
+
+nsresult
+nsFrameManagerBase::UndisplayedMap::AddNodeFor(nsIContent* aParentContent,
+ nsIContent* aChild,
+ nsStyleContext* aStyle)
+{
+ UndisplayedNode* node = new UndisplayedNode(aChild, aStyle);
+
+ AppendNodeFor(node, aParentContent);
+ return NS_OK;
+}
+
+void
+nsFrameManagerBase::UndisplayedMap::RemoveNodeFor(nsIContent* aParentContent,
+ UndisplayedNode* aNode)
+{
+ PLHashEntry** entry = GetEntryFor(&aParentContent);
+ NS_ASSERTION(*entry, "content not in map");
+ if (*entry) {
+ if ((UndisplayedNode*)((*entry)->value) == aNode) { // first node
+ if (aNode->mNext) {
+ (*entry)->value = aNode->mNext;
+ aNode->mNext = nullptr;
+ }
+ else {
+ PL_HashTableRawRemove(mTable, entry, *entry);
+ mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
+ }
+ }
+ else {
+ UndisplayedNode* node = (UndisplayedNode*)((*entry)->value);
+ while (node->mNext) {
+ if (node->mNext == aNode) {
+ node->mNext = aNode->mNext;
+ aNode->mNext = nullptr;
+ break;
+ }
+ node = node->mNext;
+ }
+ }
+ }
+ delete aNode;
+}
+
+
+UndisplayedNode*
+nsFrameManagerBase::UndisplayedMap::UnlinkNodesFor(nsIContent* aParentContent)
+{
+ PLHashEntry** entry = GetEntryFor(&aParentContent);
+ NS_ASSERTION(entry, "content not in map");
+ if (*entry) {
+ UndisplayedNode* node = (UndisplayedNode*)((*entry)->value);
+ NS_ASSERTION(node, "null node for non-null entry in UndisplayedMap");
+ PL_HashTableRawRemove(mTable, entry, *entry);
+ mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
+ return node;
+ }
+ return nullptr;
+}
+
+void
+nsFrameManagerBase::UndisplayedMap::RemoveNodesFor(nsIContent* aParentContent)
+{
+ delete UnlinkNodesFor(aParentContent);
+}
+
+static int
+RemoveUndisplayedEntry(PLHashEntry* he, int i, void* arg)
+{
+ UndisplayedNode* node = (UndisplayedNode*)(he->value);
+ delete node;
+ // Remove and free this entry and continue enumerating
+ return HT_ENUMERATE_REMOVE | HT_ENUMERATE_NEXT;
+}
+
+void
+nsFrameManagerBase::UndisplayedMap::Clear(void)
+{
+ mLastLookup = nullptr;
+ PL_HashTableEnumerateEntries(mTable, RemoveUndisplayedEntry, 0);
+}
+
+uint32_t nsFrameManagerBase::sGlobalGenerationNumber;
diff --git a/layout/base/nsFrameManager.h b/layout/base/nsFrameManager.h
new file mode 100644
index 000000000..1b9148314
--- /dev/null
+++ b/layout/base/nsFrameManager.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:cindent:ts=2:et:sw=2:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made
+ * by IBM described herein are Copyright (c) International Business Machines
+ * Corporation, 2000. Modifications to Mozilla code or documentation identified
+ * per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* storage of the frame tree and information about it */
+
+#ifndef _nsFrameManager_h_
+#define _nsFrameManager_h_
+
+#include "nsFrameManagerBase.h"
+
+#include "nsFrameList.h"
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+
+class nsContainerFrame;
+class nsPlaceholderFrame;
+
+namespace mozilla {
+/**
+ * Node in a linked list, containing the style for an element that
+ * does not have a frame but whose parent does have a frame.
+ */
+struct UndisplayedNode {
+ UndisplayedNode(nsIContent* aContent, nsStyleContext* aStyle)
+ : mContent(aContent),
+ mStyle(aStyle),
+ mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(mozilla::UndisplayedNode);
+ }
+
+ ~UndisplayedNode()
+ {
+ MOZ_COUNT_DTOR(mozilla::UndisplayedNode);
+
+ // Delete mNext iteratively to avoid blowing up the stack (bug 460461).
+ UndisplayedNode* cur = mNext;
+ while (cur) {
+ UndisplayedNode* next = cur->mNext;
+ cur->mNext = nullptr;
+ delete cur;
+ cur = next;
+ }
+ }
+
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<nsStyleContext> mStyle;
+ UndisplayedNode* mNext;
+};
+
+} // namespace mozilla
+
+/**
+ * Frame manager interface. The frame manager serves two purposes:
+ * <li>provides a service for mapping from content to frame and from
+ * out-of-flow frame to placeholder frame.
+ * <li>handles structural modifications to the frame model. If the frame model
+ * lock can be acquired, then the changes are processed immediately; otherwise,
+ * they're queued and processed later.
+ *
+ * Do not add virtual methods (a vtable pointer) or members to this class, or
+ * else you'll break the validity of the reinterpret_cast in nsIPresShell's
+ * FrameManager() method.
+ */
+
+class nsFrameManager : public nsFrameManagerBase
+{
+ typedef mozilla::layout::FrameChildListID ChildListID;
+
+public:
+ explicit nsFrameManager(nsIPresShell* aPresShell) {
+ mPresShell = aPresShell;
+ MOZ_ASSERT(mPresShell, "need a pres shell");
+ }
+ ~nsFrameManager();
+
+ /*
+ * After Destroy is called, it is an error to call any FrameManager methods.
+ * Destroy should be called when the frame tree managed by the frame
+ * manager is no longer being displayed.
+ */
+ void Destroy();
+
+ // Placeholder frame functions
+ nsPlaceholderFrame* GetPlaceholderFrameFor(const nsIFrame* aFrame);
+ void RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame);
+ void UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame);
+
+ void ClearPlaceholderFrameMap();
+
+ // Mapping undisplayed content
+ nsStyleContext* GetUndisplayedContent(nsIContent* aContent)
+ {
+ if (!mUndisplayedMap) {
+ return nullptr;
+ }
+ return GetStyleContextInMap(mUndisplayedMap, aContent);
+ }
+ mozilla::UndisplayedNode*
+ GetAllUndisplayedContentIn(nsIContent* aParentContent);
+ void SetUndisplayedContent(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+ void ChangeUndisplayedContent(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+ {
+ ChangeStyleContextInMap(mUndisplayedMap, aContent, aStyleContext);
+ }
+
+ void ClearUndisplayedContentIn(nsIContent* aContent,
+ nsIContent* aParentContent);
+ void ClearAllUndisplayedContentIn(nsIContent* aParentContent);
+
+ // display:contents related methods:
+ /**
+ * Return the registered display:contents style context for aContent, if any.
+ */
+ nsStyleContext* GetDisplayContentsStyleFor(nsIContent* aContent)
+ {
+ if (!mDisplayContentsMap) {
+ return nullptr;
+ }
+ return GetStyleContextInMap(mDisplayContentsMap, aContent);
+ }
+
+ /**
+ * Return the linked list of UndisplayedNodes containing the registered
+ * display:contents children of aParentContent, if any.
+ */
+ mozilla::UndisplayedNode* GetAllDisplayContentsIn(nsIContent* aParentContent);
+ /**
+ * Register aContent having a display:contents style context.
+ */
+ void SetDisplayContents(nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+ /**
+ * Change the registered style context for aContent to aStyleContext.
+ */
+ void ChangeDisplayContents(nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+ {
+ ChangeStyleContextInMap(mDisplayContentsMap, aContent, aStyleContext);
+ }
+
+ /**
+ * Unregister the display:contents style context for aContent, if any.
+ * If found, then also unregister any display:contents and display:none
+ * style contexts for its descendants.
+ */
+ void ClearDisplayContentsIn(nsIContent* aContent,
+ nsIContent* aParentContent);
+ void ClearAllDisplayContentsIn(nsIContent* aParentContent);
+
+ // Functions for manipulating the frame model
+ void AppendFrames(nsContainerFrame* aParentFrame,
+ ChildListID aListID,
+ nsFrameList& aFrameList);
+
+ void InsertFrames(nsContainerFrame* aParentFrame,
+ ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList);
+
+ void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame);
+
+ /*
+ * Notification that a frame is about to be destroyed. This allows any
+ * outstanding references to the frame to be cleaned up.
+ */
+ void NotifyDestroyingFrame(nsIFrame* aFrame);
+
+ /*
+ * Capture/restore frame state for the frame subtree rooted at aFrame.
+ * aState is the document state storage object onto which each frame
+ * stores its state. Callers of CaptureFrameState are responsible for
+ * traversing next continuations of special siblings of aFrame as
+ * needed; this method will only work with actual frametree descendants
+ * of aFrame.
+ */
+
+ void CaptureFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState);
+
+ void RestoreFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState);
+
+ /*
+ * Add/restore state for one frame
+ */
+ void CaptureFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState);
+
+ void RestoreFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState);
+protected:
+ static nsStyleContext* GetStyleContextInMap(UndisplayedMap* aMap,
+ nsIContent* aContent);
+ static mozilla::UndisplayedNode*
+ GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
+ nsIContent* aParentContent);
+ static void SetStyleContextInMap(UndisplayedMap* aMap,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+ static void ChangeStyleContextInMap(UndisplayedMap* aMap,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext);
+};
+
+#endif
diff --git a/layout/base/nsFrameManagerBase.h b/layout/base/nsFrameManagerBase.h
new file mode 100644
index 000000000..757dce7e5
--- /dev/null
+++ b/layout/base/nsFrameManagerBase.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:cindent:ts=2:et:sw=2:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made
+ * by IBM described herein are Copyright (c) International Business Machines
+ * Corporation, 2000. Modifications to Mozilla code or documentation identified
+ * per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* part of nsFrameManager, to work around header inclusionordering */
+
+#ifndef _nsFrameManagerBase_h_
+#define _nsFrameManagerBase_h_
+
+#include "nsDebug.h"
+#include "PLDHashTable.h"
+#include "mozilla/Attributes.h"
+
+class nsIFrame;
+class nsIPresShell;
+
+class nsFrameManagerBase
+{
+public:
+ nsFrameManagerBase();
+
+ bool IsDestroyingFrames() { return mIsDestroyingFrames; }
+
+ /*
+ * Gets and sets the root frame (typically the viewport). The lifetime of the
+ * root frame is controlled by the frame manager. When the frame manager is
+ * destroyed, it destroys the entire frame hierarchy.
+ */
+ nsIFrame* GetRootFrame() const { return mRootFrame; }
+ void SetRootFrame(nsIFrame* aRootFrame)
+ {
+ NS_ASSERTION(!mRootFrame, "already have a root frame");
+ mRootFrame = aRootFrame;
+ }
+
+ static uint32_t GetGlobalGenerationNumber() { return sGlobalGenerationNumber; }
+
+protected:
+ class UndisplayedMap;
+
+ // weak link, because the pres shell owns us
+ nsIPresShell* MOZ_NON_OWNING_REF mPresShell;
+ nsIFrame* mRootFrame;
+ PLDHashTable mPlaceholderMap;
+ UndisplayedMap* mUndisplayedMap;
+ UndisplayedMap* mDisplayContentsMap;
+ bool mIsDestroyingFrames; // The frame manager is destroying some frame(s).
+
+ // The frame tree generation number
+ // We use this to avoid unnecessary screenshotting
+ // on Android. Unfortunately, this is static to match
+ // the single consumer which is also static. Keeping
+ // this the same greatly simplifies lifetime issues and
+ // makes sure we always using the correct number.
+ // A per PresContext generation number is available
+ // via nsPresContext::GetDOMGeneration
+ static uint32_t sGlobalGenerationNumber;
+};
+
+#endif
diff --git a/layout/base/nsFrameTraversal.cpp b/layout/base/nsFrameTraversal.cpp
new file mode 100644
index 000000000..40fb5ff30
--- /dev/null
+++ b/layout/base/nsFrameTraversal.cpp
@@ -0,0 +1,541 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "nsFrameTraversal.h"
+#include "nsFrameList.h"
+#include "nsPlaceholderFrame.h"
+#include "nsContainerFrame.h"
+
+
+class nsFrameIterator : public nsIFrameEnumerator
+{
+public:
+ typedef nsIFrame::ChildListID ChildListID;
+
+ NS_DECL_ISUPPORTS
+
+ virtual void First() override;
+ virtual void Next() override;
+ virtual nsIFrame* CurrentItem() override;
+ virtual bool IsDone() override;
+
+ virtual void Last() override;
+ virtual void Prev() override;
+
+ nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart,
+ nsIteratorType aType, bool aLockScroll, bool aFollowOOFs,
+ bool aSkipPopupChecks);
+
+protected:
+ virtual ~nsFrameIterator() {}
+
+ void setCurrent(nsIFrame *aFrame){mCurrent = aFrame;}
+ nsIFrame *getCurrent(){return mCurrent;}
+ nsIFrame *getStart(){return mStart;}
+ nsIFrame *getLast(){return mLast;}
+ void setLast(nsIFrame *aFrame){mLast = aFrame;}
+ int8_t getOffEdge(){return mOffEdge;}
+ void setOffEdge(int8_t aOffEdge){mOffEdge = aOffEdge;}
+
+ /*
+ Our own versions of the standard frame tree navigation
+ methods, which, if the iterator is following out-of-flows,
+ apply the following rules for placeholder frames:
+
+ - If a frame HAS a placeholder frame, getting its parent
+ gets the placeholder's parent.
+
+ - If a frame's first child or next/prev sibling IS a
+ placeholder frame, then we instead return the real frame.
+
+ - If a frame HAS a placeholder frame, getting its next/prev
+ sibling gets the placeholder frame's next/prev sibling.
+
+ These are all applied recursively to support multiple levels of
+ placeholders.
+ */
+
+ nsIFrame* GetParentFrame(nsIFrame* aFrame);
+ // like GetParentFrame but returns null once a popup frame is reached
+ nsIFrame* GetParentFrameNotPopup(nsIFrame* aFrame);
+
+ nsIFrame* GetFirstChild(nsIFrame* aFrame);
+ nsIFrame* GetLastChild(nsIFrame* aFrame);
+
+ nsIFrame* GetNextSibling(nsIFrame* aFrame);
+ nsIFrame* GetPrevSibling(nsIFrame* aFrame);
+
+ /*
+ These methods are overridden by the bidi visual iterator to have the
+ semantics of "get first child in visual order", "get last child in visual
+ order", "get next sibling in visual order" and "get previous sibling in visual
+ order".
+ */
+
+ virtual nsIFrame* GetFirstChildInner(nsIFrame* aFrame);
+ virtual nsIFrame* GetLastChildInner(nsIFrame* aFrame);
+
+ virtual nsIFrame* GetNextSiblingInner(nsIFrame* aFrame);
+ virtual nsIFrame* GetPrevSiblingInner(nsIFrame* aFrame);
+
+ nsIFrame* GetPlaceholderFrame(nsIFrame* aFrame);
+ bool IsPopupFrame(nsIFrame* aFrame);
+
+ nsPresContext* const mPresContext;
+ const bool mLockScroll;
+ const bool mFollowOOFs;
+ const bool mSkipPopupChecks;
+ const nsIteratorType mType;
+
+private:
+ nsIFrame* const mStart;
+ nsIFrame* mCurrent;
+ nsIFrame* mLast; //the last one that was in current;
+ int8_t mOffEdge; //0= no -1 to far prev, 1 to far next;
+};
+
+
+
+// Bidi visual iterator
+class nsVisualIterator: public nsFrameIterator
+{
+public:
+ nsVisualIterator(nsPresContext* aPresContext, nsIFrame *aStart,
+ nsIteratorType aType, bool aLockScroll,
+ bool aFollowOOFs, bool aSkipPopupChecks) :
+ nsFrameIterator(aPresContext, aStart, aType, aLockScroll, aFollowOOFs, aSkipPopupChecks) {}
+
+protected:
+ nsIFrame* GetFirstChildInner(nsIFrame* aFrame) override;
+ nsIFrame* GetLastChildInner(nsIFrame* aFrame) override;
+
+ nsIFrame* GetNextSiblingInner(nsIFrame* aFrame) override;
+ nsIFrame* GetPrevSiblingInner(nsIFrame* aFrame) override;
+};
+
+/************IMPLEMENTATIONS**************/
+
+nsresult
+NS_CreateFrameTraversal(nsIFrameTraversal** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIFrameTraversal> t = new nsFrameTraversal();
+ t.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
+ nsPresContext* aPresContext,
+ nsIFrame *aStart,
+ nsIteratorType aType,
+ bool aVisual,
+ bool aLockInScrollView,
+ bool aFollowOOFs,
+ bool aSkipPopupChecks)
+{
+ if (!aEnumerator || !aStart)
+ return NS_ERROR_NULL_POINTER;
+
+ if (aFollowOOFs) {
+ aStart = nsPlaceholderFrame::GetRealFrameFor(aStart);
+ }
+
+ nsCOMPtr<nsIFrameEnumerator> trav;
+ if (aVisual) {
+ trav = new nsVisualIterator(aPresContext, aStart, aType,
+ aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
+ } else {
+ trav = new nsFrameIterator(aPresContext, aStart, aType,
+ aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
+ }
+ trav.forget(aEnumerator);
+ return NS_OK;
+}
+
+
+nsFrameTraversal::nsFrameTraversal()
+{
+}
+
+nsFrameTraversal::~nsFrameTraversal()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsFrameTraversal,nsIFrameTraversal)
+
+NS_IMETHODIMP
+ nsFrameTraversal::NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
+ nsPresContext* aPresContext,
+ nsIFrame *aStart,
+ int32_t aType,
+ bool aVisual,
+ bool aLockInScrollView,
+ bool aFollowOOFs,
+ bool aSkipPopupChecks)
+{
+ return NS_NewFrameTraversal(aEnumerator, aPresContext, aStart,
+ static_cast<nsIteratorType>(aType),
+ aVisual, aLockInScrollView, aFollowOOFs, aSkipPopupChecks);
+}
+
+// nsFrameIterator implementation
+
+NS_IMPL_ISUPPORTS(nsFrameIterator, nsIFrameEnumerator)
+
+nsFrameIterator::nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart,
+ nsIteratorType aType, bool aLockInScrollView,
+ bool aFollowOOFs, bool aSkipPopupChecks)
+: mPresContext(aPresContext),
+ mLockScroll(aLockInScrollView),
+ mFollowOOFs(aFollowOOFs),
+ mSkipPopupChecks(aSkipPopupChecks),
+ mType(aType),
+ mStart(aStart),
+ mCurrent(aStart),
+ mLast(aStart),
+ mOffEdge(0)
+{
+ MOZ_ASSERT(!aFollowOOFs || aStart->GetType() != nsGkAtoms::placeholderFrame,
+ "Caller should have resolved placeholder frame");
+}
+
+
+
+nsIFrame*
+nsFrameIterator::CurrentItem()
+{
+ if (mOffEdge)
+ return nullptr;
+
+ return mCurrent;
+}
+
+
+
+bool
+nsFrameIterator::IsDone()
+{
+ return mOffEdge != 0;
+}
+
+void
+nsFrameIterator::First()
+{
+ mCurrent = mStart;
+}
+
+static bool
+IsRootFrame(nsIFrame* aFrame)
+{
+ nsIAtom* atom = aFrame->GetType();
+ return (atom == nsGkAtoms::canvasFrame) ||
+ (atom == nsGkAtoms::rootFrame);
+}
+
+void
+nsFrameIterator::Last()
+{
+ nsIFrame* result;
+ nsIFrame* parent = getCurrent();
+ // If the current frame is a popup, don't move farther up the tree.
+ // Otherwise, get the nearest root frame or popup.
+ if (mSkipPopupChecks || parent->GetType() != nsGkAtoms::menuPopupFrame) {
+ while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent)))
+ parent = result;
+ }
+
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+
+ setCurrent(parent);
+ if (!parent)
+ setOffEdge(1);
+}
+
+void
+nsFrameIterator::Next()
+{
+ // recursive-oid method to get next frame
+ nsIFrame *result = nullptr;
+ nsIFrame *parent = getCurrent();
+ if (!parent)
+ parent = getLast();
+
+ if (mType == eLeaf) {
+ // Drill down to first leaf
+ while ((result = GetFirstChild(parent))) {
+ parent = result;
+ }
+ } else if (mType == ePreOrder) {
+ result = GetFirstChild(parent);
+ if (result)
+ parent = result;
+ }
+
+ if (parent != getCurrent()) {
+ result = parent;
+ } else {
+ while (parent) {
+ result = GetNextSibling(parent);
+ if (result) {
+ if (mType != ePreOrder) {
+ parent = result;
+ while ((result = GetFirstChild(parent))) {
+ parent = result;
+ }
+ result = parent;
+ }
+ break;
+ }
+ else {
+ result = GetParentFrameNotPopup(parent);
+ if (!result || IsRootFrame(result) ||
+ (mLockScroll && result->GetType() == nsGkAtoms::scrollFrame)) {
+ result = nullptr;
+ break;
+ }
+ if (mType == ePostOrder)
+ break;
+ parent = result;
+ }
+ }
+ }
+
+ setCurrent(result);
+ if (!result) {
+ setOffEdge(1);
+ setLast(parent);
+ }
+}
+
+void
+nsFrameIterator::Prev()
+{
+ // recursive-oid method to get prev frame
+ nsIFrame *result = nullptr;
+ nsIFrame *parent = getCurrent();
+ if (!parent)
+ parent = getLast();
+
+ if (mType == eLeaf) {
+ // Drill down to last leaf
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+ } else if (mType == ePostOrder) {
+ result = GetLastChild(parent);
+ if (result)
+ parent = result;
+ }
+
+ if (parent != getCurrent()) {
+ result = parent;
+ } else {
+ while (parent) {
+ result = GetPrevSibling(parent);
+ if (result) {
+ if (mType != ePostOrder) {
+ parent = result;
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+ result = parent;
+ }
+ break;
+ } else {
+ result = GetParentFrameNotPopup(parent);
+ if (!result || IsRootFrame(result) ||
+ (mLockScroll && result->GetType() == nsGkAtoms::scrollFrame)) {
+ result = nullptr;
+ break;
+ }
+ if (mType == ePreOrder)
+ break;
+ parent = result;
+ }
+ }
+ }
+
+ setCurrent(result);
+ if (!result) {
+ setOffEdge(-1);
+ setLast(parent);
+ }
+}
+
+nsIFrame*
+nsFrameIterator::GetParentFrame(nsIFrame* aFrame)
+{
+ if (mFollowOOFs)
+ aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame)
+ return aFrame->GetParent();
+
+ return nullptr;
+}
+
+nsIFrame*
+nsFrameIterator::GetParentFrameNotPopup(nsIFrame* aFrame)
+{
+ if (mFollowOOFs)
+ aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame) {
+ nsIFrame* parent = aFrame->GetParent();
+ if (!IsPopupFrame(parent))
+ return parent;
+ }
+
+ return nullptr;
+}
+
+nsIFrame*
+nsFrameIterator::GetFirstChild(nsIFrame* aFrame)
+{
+ nsIFrame* result = GetFirstChildInner(aFrame);
+ if (mLockScroll && result && result->GetType() == nsGkAtoms::scrollFrame)
+ return nullptr;
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+
+ if (IsPopupFrame(result))
+ result = GetNextSibling(result);
+ }
+ return result;
+}
+
+nsIFrame*
+nsFrameIterator::GetLastChild(nsIFrame* aFrame)
+{
+ nsIFrame* result = GetLastChildInner(aFrame);
+ if (mLockScroll && result && result->GetType() == nsGkAtoms::scrollFrame)
+ return nullptr;
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+
+ if (IsPopupFrame(result))
+ result = GetPrevSibling(result);
+ }
+ return result;
+}
+
+nsIFrame*
+nsFrameIterator::GetNextSibling(nsIFrame* aFrame)
+{
+ nsIFrame* result = nullptr;
+ if (mFollowOOFs)
+ aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame) {
+ result = GetNextSiblingInner(aFrame);
+ if (result && mFollowOOFs)
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+ }
+
+ if (mFollowOOFs && IsPopupFrame(result))
+ result = GetNextSibling(result);
+
+ return result;
+}
+
+nsIFrame*
+nsFrameIterator::GetPrevSibling(nsIFrame* aFrame)
+{
+ nsIFrame* result = nullptr;
+ if (mFollowOOFs)
+ aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame) {
+ result = GetPrevSiblingInner(aFrame);
+ if (result && mFollowOOFs)
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+ }
+
+ if (mFollowOOFs && IsPopupFrame(result))
+ result = GetPrevSibling(result);
+
+ return result;
+}
+
+nsIFrame*
+nsFrameIterator::GetFirstChildInner(nsIFrame* aFrame) {
+ return aFrame->PrincipalChildList().FirstChild();
+}
+
+nsIFrame*
+nsFrameIterator::GetLastChildInner(nsIFrame* aFrame) {
+ return aFrame->PrincipalChildList().LastChild();
+}
+
+nsIFrame*
+nsFrameIterator::GetNextSiblingInner(nsIFrame* aFrame) {
+ return aFrame->GetNextSibling();
+}
+
+nsIFrame*
+nsFrameIterator::GetPrevSiblingInner(nsIFrame* aFrame) {
+ return aFrame->GetPrevSibling();
+}
+
+
+nsIFrame*
+nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame)
+{
+ nsIFrame* result = aFrame;
+ nsIPresShell *presShell = mPresContext->GetPresShell();
+ if (presShell) {
+ nsIFrame* placeholder = presShell->GetPlaceholderFrameFor(aFrame);
+ if (placeholder)
+ result = placeholder;
+ }
+
+ if (result != aFrame)
+ result = GetPlaceholderFrame(result);
+
+ return result;
+}
+
+bool
+nsFrameIterator::IsPopupFrame(nsIFrame* aFrame)
+{
+ // If skipping popup checks, pretend this isn't one.
+ if (mSkipPopupChecks) {
+ return false;
+ }
+
+ return (aFrame &&
+ aFrame->StyleDisplay()->mDisplay == StyleDisplay::Popup);
+}
+
+// nsVisualIterator implementation
+
+nsIFrame*
+nsVisualIterator::GetFirstChildInner(nsIFrame* aFrame) {
+ return aFrame->PrincipalChildList().GetNextVisualFor(nullptr);
+}
+
+nsIFrame*
+nsVisualIterator::GetLastChildInner(nsIFrame* aFrame) {
+ return aFrame->PrincipalChildList().GetPrevVisualFor(nullptr);
+}
+
+nsIFrame*
+nsVisualIterator::GetNextSiblingInner(nsIFrame* aFrame) {
+ nsIFrame* parent = GetParentFrame(aFrame);
+ if (!parent)
+ return nullptr;
+ return parent->PrincipalChildList().GetNextVisualFor(aFrame);
+}
+
+nsIFrame*
+nsVisualIterator::GetPrevSiblingInner(nsIFrame* aFrame) {
+ nsIFrame* parent = GetParentFrame(aFrame);
+ if (!parent)
+ return nullptr;
+ return parent->PrincipalChildList().GetPrevVisualFor(aFrame);
+}
diff --git a/layout/base/nsFrameTraversal.h b/layout/base/nsFrameTraversal.h
new file mode 100644
index 000000000..d0d94e60f
--- /dev/null
+++ b/layout/base/nsFrameTraversal.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSFRAMETRAVERSAL_H
+#define NSFRAMETRAVERSAL_H
+
+#include "mozilla/Attributes.h"
+#include "nsIFrameTraversal.h"
+
+class nsIFrame;
+
+nsresult NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
+ nsPresContext* aPresContext,
+ nsIFrame *aStart,
+ nsIteratorType aType,
+ bool aVisual,
+ bool aLockInScrollView,
+ bool aFollowOOFs,
+ bool aSkipPopupChecks);
+
+nsresult NS_CreateFrameTraversal(nsIFrameTraversal** aResult);
+
+class nsFrameTraversal : public nsIFrameTraversal
+{
+public:
+ nsFrameTraversal();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
+ nsPresContext* aPresContext,
+ nsIFrame *aStart,
+ int32_t aType,
+ bool aVisual,
+ bool aLockInScrollView,
+ bool aFollowOOFs,
+ bool aSkipPopupChecks) override;
+
+protected:
+ virtual ~nsFrameTraversal();
+};
+
+#endif //NSFRAMETRAVERSAL_H
diff --git a/layout/base/nsGenConList.cpp b/layout/base/nsGenConList.cpp
new file mode 100644
index 000000000..bdb47e183
--- /dev/null
+++ b/layout/base/nsGenConList.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for nsCounterList and nsQuoteList */
+
+#include "nsGenConList.h"
+#include "nsLayoutUtils.h"
+#include "nsIContent.h"
+
+void
+nsGenConList::Clear()
+{
+ // Delete entire list.
+ mNodes.Clear();
+ while (nsGenConNode* node = mList.popFirst()) {
+ delete node;
+ }
+ mSize = 0;
+}
+
+bool
+nsGenConList::DestroyNodesFor(nsIFrame* aFrame)
+{
+ // This algorithm relies on the invariant that nodes of a frame are
+ // put contiguously in the linked list. This is guaranteed because
+ // each frame is mapped to only one (nsIContent, pseudoType) pair,
+ // and the nodes in the linked list are put in the tree order based
+ // on that pair and offset inside frame.
+ nsGenConNode* node = mNodes.GetAndRemove(aFrame).valueOr(nullptr);
+ if (!node) {
+ return false;
+ }
+ MOZ_ASSERT(node->mPseudoFrame == aFrame);
+
+ while (node && node->mPseudoFrame == aFrame) {
+ nsGenConNode* nextNode = Next(node);
+ Destroy(node);
+ node = nextNode;
+ }
+
+ return true;
+}
+
+/**
+ * Compute the type of the pseudo and the content for the pseudo that
+ * we'll use for comparison purposes.
+ * @param aContent the content to use is stored here; it's the element
+ * that generated the ::before or ::after content, or (if not for generated
+ * content), the frame's own element
+ * @return -1 for ::before, +1 for ::after, and 0 otherwise.
+ */
+inline int32_t PseudoCompareType(nsIFrame* aFrame, nsIContent** aContent)
+{
+ nsIAtom *pseudo = aFrame->StyleContext()->GetPseudo();
+ if (pseudo == nsCSSPseudoElements::before) {
+ *aContent = aFrame->GetContent()->GetParent();
+ return -1;
+ }
+ if (pseudo == nsCSSPseudoElements::after) {
+ *aContent = aFrame->GetContent()->GetParent();
+ return 1;
+ }
+ *aContent = aFrame->GetContent();
+ return 0;
+}
+
+/* static */ bool
+nsGenConList::NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2)
+{
+ nsIFrame *frame1 = aNode1->mPseudoFrame;
+ nsIFrame *frame2 = aNode2->mPseudoFrame;
+ if (frame1 == frame2) {
+ NS_ASSERTION(aNode2->mContentIndex != aNode1->mContentIndex, "identical");
+ return aNode1->mContentIndex > aNode2->mContentIndex;
+ }
+ nsIContent *content1;
+ nsIContent *content2;
+ int32_t pseudoType1 = PseudoCompareType(frame1, &content1);
+ int32_t pseudoType2 = PseudoCompareType(frame2, &content2);
+ if (pseudoType1 == 0 || pseudoType2 == 0) {
+ if (content1 == content2) {
+ NS_ASSERTION(pseudoType1 != pseudoType2, "identical");
+ return pseudoType2 == 0;
+ }
+ // We want to treat an element as coming before its :before (preorder
+ // traversal), so treating both as :before now works.
+ if (pseudoType1 == 0) pseudoType1 = -1;
+ if (pseudoType2 == 0) pseudoType2 = -1;
+ } else {
+ if (content1 == content2) {
+ NS_ASSERTION(pseudoType1 != pseudoType2, "identical");
+ return pseudoType1 == 1;
+ }
+ }
+ // XXX Switch to the frame version of DoCompareTreePosition?
+ int32_t cmp = nsLayoutUtils::DoCompareTreePosition(content1, content2,
+ pseudoType1, -pseudoType2);
+ MOZ_ASSERT(cmp != 0, "same content, different frames");
+ return cmp > 0;
+}
+
+void
+nsGenConList::Insert(nsGenConNode* aNode)
+{
+ // Check for append.
+ if (mList.isEmpty() || NodeAfter(aNode, mList.getLast())) {
+ mList.insertBack(aNode);
+ } else {
+ // Binary search.
+
+ // the range of indices at which |aNode| could end up.
+ // (We already know it can't be at index mSize.)
+ uint32_t first = 0, last = mSize - 1;
+
+ // A cursor to avoid walking more than the length of the list.
+ nsGenConNode* curNode = mList.getLast();
+ uint32_t curIndex = mSize - 1;
+
+ while (first != last) {
+ uint32_t test = (first + last) / 2;
+ if (last == curIndex) {
+ for ( ; curIndex != test; --curIndex)
+ curNode = Prev(curNode);
+ } else {
+ for ( ; curIndex != test; ++curIndex)
+ curNode = Next(curNode);
+ }
+
+ if (NodeAfter(aNode, curNode)) {
+ first = test + 1;
+ // if we exit the loop, we need curNode to be right
+ ++curIndex;
+ curNode = Next(curNode);
+ } else {
+ last = test;
+ }
+ }
+ curNode->setPrevious(aNode);
+ }
+ ++mSize;
+
+ // Set the mapping only if it is the first node of the frame.
+ // The DEBUG blocks below are for ensuring the invariant required by
+ // nsGenConList::DestroyNodesFor. See comment there.
+ if (IsFirst(aNode) ||
+ Prev(aNode)->mPseudoFrame != aNode->mPseudoFrame) {
+#ifdef DEBUG
+ if (nsGenConNode* oldFrameFirstNode = mNodes.Get(aNode->mPseudoFrame)) {
+ MOZ_ASSERT(Next(aNode) == oldFrameFirstNode,
+ "oldFrameFirstNode should now be immediately after "
+ "the newly-inserted one.");
+ } else {
+ // If the node is not the only node in the list.
+ if (!IsFirst(aNode) || !IsLast(aNode)) {
+ nsGenConNode* nextNode = Next(aNode);
+ MOZ_ASSERT(!nextNode || nextNode->mPseudoFrame != aNode->mPseudoFrame,
+ "There shouldn't exist any node for this frame.");
+ // If the node is neither the first nor the last node
+ if (!IsFirst(aNode) && !IsLast(aNode)) {
+ MOZ_ASSERT(Prev(aNode)->mPseudoFrame != nextNode->mPseudoFrame,
+ "New node should not break contiguity of nodes of "
+ "the same frame.");
+ }
+ }
+ }
+#endif
+ mNodes.Put(aNode->mPseudoFrame, aNode);
+ } else {
+#ifdef DEBUG
+ nsGenConNode* frameFirstNode = mNodes.Get(aNode->mPseudoFrame);
+ MOZ_ASSERT(frameFirstNode, "There should exist node map for the frame.");
+ for (nsGenConNode* curNode = Prev(aNode);
+ curNode != frameFirstNode; curNode = Prev(curNode)) {
+ MOZ_ASSERT(curNode->mPseudoFrame == aNode->mPseudoFrame,
+ "Every node between frameFirstNode and the new node inserted "
+ "should refer to the same frame.");
+ MOZ_ASSERT(!IsFirst(curNode),
+ "The newly-inserted node should be in a contiguous run after "
+ "frameFirstNode, thus frameFirstNode should be reached before "
+ "the first node of mList.");
+ }
+#endif
+ }
+
+ NS_ASSERTION(IsFirst(aNode) || NodeAfter(aNode, Prev(aNode)),
+ "sorting error");
+ NS_ASSERTION(IsLast(aNode) || NodeAfter(Next(aNode), aNode),
+ "sorting error");
+}
diff --git a/layout/base/nsGenConList.h b/layout/base/nsGenConList.h
new file mode 100644
index 000000000..55bf6bc32
--- /dev/null
+++ b/layout/base/nsGenConList.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for nsCounterList and nsQuoteList */
+
+#ifndef nsGenConList_h___
+#define nsGenConList_h___
+
+#include "mozilla/LinkedList.h"
+#include "nsIFrame.h"
+#include "nsStyleStruct.h"
+#include "nsCSSPseudoElements.h"
+#include "nsTextNode.h"
+
+class nsGenConList;
+
+struct nsGenConNode : public mozilla::LinkedListElement<nsGenConNode> {
+ // The wrapper frame for all of the pseudo-element's content. This
+ // frame generally has useful style data and has the
+ // NS_FRAME_GENERATED_CONTENT bit set (so we use it to track removal),
+ // but does not necessarily for |nsCounterChangeNode|s.
+ nsIFrame* mPseudoFrame;
+
+ // Index within the list of things specified by the 'content' property,
+ // which is needed to do 'content: open-quote open-quote' correctly,
+ // and needed for similar cases for counters.
+ const int32_t mContentIndex;
+
+ // null for 'content:no-open-quote', 'content:no-close-quote' and for
+ // counter nodes for increments and resets (rather than uses)
+ RefPtr<nsTextNode> mText;
+
+ explicit nsGenConNode(int32_t aContentIndex)
+ : mPseudoFrame(nullptr)
+ , mContentIndex(aContentIndex)
+ {
+ }
+
+ /**
+ * Finish initializing the generated content node once we know the
+ * relevant text frame. This must be called just after
+ * the textframe has been initialized. This need not be called at all
+ * for nodes that don't generate text. This will generally set the
+ * mPseudoFrame, insert the node into aList, and set aTextFrame up
+ * with the correct text.
+ * @param aList the list the node belongs to
+ * @param aPseudoFrame the :before or :after frame
+ * @param aTextFrame the textframe where the node contents will render
+ * @return true iff this marked the list dirty
+ */
+ virtual bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame)
+ {
+ mPseudoFrame = aPseudoFrame;
+ CheckFrameAssertions();
+ return false;
+ }
+
+ virtual ~nsGenConNode() {} // XXX Avoid, perhaps?
+
+protected:
+ void CheckFrameAssertions() {
+ NS_ASSERTION(mContentIndex <
+ int32_t(mPseudoFrame->StyleContent()->ContentCount()),
+ "index out of range");
+ // We allow negative values of mContentIndex for 'counter-reset' and
+ // 'counter-increment'.
+
+ NS_ASSERTION(mContentIndex < 0 ||
+ mPseudoFrame->StyleContext()->GetPseudo() ==
+ nsCSSPseudoElements::before ||
+ mPseudoFrame->StyleContext()->GetPseudo() ==
+ nsCSSPseudoElements::after,
+ "not :before/:after generated content and not counter change");
+ NS_ASSERTION(mContentIndex < 0 ||
+ mPseudoFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT,
+ "not generated content and not counter change");
+ }
+};
+
+class nsGenConList {
+protected:
+ mozilla::LinkedList<nsGenConNode> mList;
+ uint32_t mSize;
+
+public:
+ nsGenConList() : mSize(0) {}
+ ~nsGenConList() { Clear(); }
+ void Clear();
+ static nsGenConNode* Next(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode->getNext();
+ }
+ static nsGenConNode* Prev(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode->getPrevious();
+ }
+ void Insert(nsGenConNode* aNode);
+
+ // Destroy all nodes with aFrame as parent. Returns true if some nodes
+ // have been destroyed; otherwise false.
+ bool DestroyNodesFor(nsIFrame* aFrame);
+
+ // Return true if |aNode1| is after |aNode2|.
+ static bool NodeAfter(const nsGenConNode* aNode1,
+ const nsGenConNode* aNode2);
+
+ bool IsFirst(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode == mList.getFirst();
+ }
+
+ bool IsLast(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode == mList.getLast();
+ }
+
+private:
+ void Destroy(nsGenConNode* aNode)
+ {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ delete aNode;
+ mSize--;
+ }
+
+ // Map from frame to the first nsGenConNode of it in the list.
+ nsDataHashtable<nsPtrHashKey<nsIFrame>, nsGenConNode*> mNodes;
+};
+
+#endif /* nsGenConList_h___ */
diff --git a/layout/base/nsIDocumentViewerPrint.h b/layout/base/nsIDocumentViewerPrint.h
new file mode 100644
index 000000000..8bc2465a1
--- /dev/null
+++ b/layout/base/nsIDocumentViewerPrint.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIDocumentViewerPrint_h___
+#define nsIDocumentViewerPrint_h___
+
+#include "nsISupports.h"
+
+class nsIDocument;
+namespace mozilla {
+class StyleSetHandle;
+} // namespace mozilla
+class nsIPresShell;
+class nsPresContext;
+class nsViewManager;
+
+// {c6f255cf-cadd-4382-b57f-cd2a9874169b}
+#define NS_IDOCUMENT_VIEWER_PRINT_IID \
+{ 0xc6f255cf, 0xcadd, 0x4382, \
+ { 0xb5, 0x7f, 0xcd, 0x2a, 0x98, 0x74, 0x16, 0x9b } }
+
+/**
+ * A DocumentViewerPrint is an INTERNAL Interface used for interaction
+ * between the DocumentViewer and the PrintEngine
+ */
+class nsIDocumentViewerPrint : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_VIEWER_PRINT_IID)
+
+ virtual void SetIsPrinting(bool aIsPrinting) = 0;
+ virtual bool GetIsPrinting() = 0;
+
+ virtual void SetIsPrintPreview(bool aIsPrintPreview) = 0;
+ virtual bool GetIsPrintPreview() = 0;
+
+ // The style set returned by CreateStyleSet is in the middle of an
+ // update batch so that the caller can add sheets to it if needed.
+ // Callers should call EndUpdate() on it when ready to use.
+ virtual mozilla::StyleSetHandle CreateStyleSet(nsIDocument* aDocument) = 0;
+
+ virtual void IncrementDestroyRefCount() = 0;
+
+ virtual void ReturnToGalleyPresentation() = 0;
+
+ virtual void OnDonePrinting() = 0;
+
+ /**
+ * Returns true is InitializeForPrintPreview() has been called.
+ */
+ virtual bool IsInitializedForPrintPreview() = 0;
+
+ /**
+ * Marks this viewer to be used for print preview.
+ */
+ virtual void InitializeForPrintPreview() = 0;
+
+ /**
+ * Replaces the current presentation with print preview presentation.
+ */
+ virtual void SetPrintPreviewPresentation(nsViewManager* aViewManager,
+ nsPresContext* aPresContext,
+ nsIPresShell* aPresShell) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentViewerPrint,
+ NS_IDOCUMENT_VIEWER_PRINT_IID)
+
+/* Use this macro when declaring classes that implement this interface. */
+#define NS_DECL_NSIDOCUMENTVIEWERPRINT \
+ void SetIsPrinting(bool aIsPrinting) override; \
+ bool GetIsPrinting() override; \
+ void SetIsPrintPreview(bool aIsPrintPreview) override; \
+ bool GetIsPrintPreview() override; \
+ mozilla::StyleSetHandle CreateStyleSet(nsIDocument* aDocument) override; \
+ void IncrementDestroyRefCount() override; \
+ void ReturnToGalleyPresentation() override; \
+ void OnDonePrinting() override; \
+ bool IsInitializedForPrintPreview() override; \
+ void InitializeForPrintPreview() override; \
+ void SetPrintPreviewPresentation(nsViewManager* aViewManager, \
+ nsPresContext* aPresContext, \
+ nsIPresShell* aPresShell) override;
+
+#endif /* nsIDocumentViewerPrint_h___ */
diff --git a/layout/base/nsIFrameTraversal.h b/layout/base/nsIFrameTraversal.h
new file mode 100644
index 000000000..29e007138
--- /dev/null
+++ b/layout/base/nsIFrameTraversal.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSIFRAMETRAVERSAL_H
+#define NSIFRAMETRAVERSAL_H
+
+#include "nsISupports.h"
+#include "nsIFrame.h"
+
+#define NS_IFRAMEENUMERATOR_IID \
+{ 0x7c633f5d, 0x91eb, 0x494e, \
+ { 0xa1, 0x40, 0x17, 0x46, 0x17, 0x4c, 0x23, 0xd3 } }
+
+class nsIFrameEnumerator : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFRAMEENUMERATOR_IID)
+
+ virtual void First() = 0;
+ virtual void Next() = 0;
+ virtual nsIFrame* CurrentItem() = 0;
+ virtual bool IsDone() = 0;
+
+ virtual void Last() = 0;
+ virtual void Prev() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIFrameEnumerator, NS_IFRAMEENUMERATOR_IID)
+
+enum nsIteratorType {
+ eLeaf,
+ ePreOrder,
+ ePostOrder
+};
+
+// {d33fe76c-207c-4359-a315-8eb1eecf80e5}
+#define NS_IFRAMETRAVERSAL_IID \
+{ 0xd33fe76c, 0x207c, 0x4359, { 0xa3, 0x15, 0x8e, 0xb1, 0xee, 0xcf, 0x80, 0xe5 } }
+
+class nsIFrameTraversal : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFRAMETRAVERSAL_IID)
+
+ /**
+ * Create a frame iterator with the specified properties.
+ * @param aEnumerator [out] the created iterator
+ * @param aPresContext [in]
+ * @param aStart [in] the frame to start iterating from
+ * @param aType [in] the type of the iterator: leaf, pre-order, or post-order
+ * @param aVisual [in] whether the iterator should traverse frames in visual
+ * bidi order
+ * @param aLockInScrollView [in] whether to stop iterating when exiting a
+ * scroll view
+ * @param aFollowOOFs [in] whether the iterator should follow out-of-flows.
+ * If true, when reaching a placeholder frame while going down will get
+ * the real frame. Going back up will go on past the placeholder,
+ * so the placeholders are logically part of the frame tree.
+ * @param aSkipPopupChecks [in] if false, then don't iterate into or out of a
+ * popup frame. If true, skip any popup related checks.
+ */
+ NS_IMETHOD NewFrameTraversal(nsIFrameEnumerator **aEnumerator,
+ nsPresContext* aPresContext,
+ nsIFrame *aStart,
+ int32_t aType,
+ bool aVisual,
+ bool aLockInScrollView,
+ bool aFollowOOFs,
+ bool aSkipPopupChecks) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIFrameTraversal, NS_IFRAMETRAVERSAL_IID)
+
+#endif //NSIFRAMETRAVERSAL_H
diff --git a/layout/base/nsILayoutDebugger.h b/layout/base/nsILayoutDebugger.h
new file mode 100644
index 000000000..d27456641
--- /dev/null
+++ b/layout/base/nsILayoutDebugger.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* XPCOM interface for layout-debug extension to reach layout internals */
+
+#ifndef nsILayoutDebugger_h___
+#define nsILayoutDebugger_h___
+
+#include "nsISupports.h"
+
+class nsIDocument;
+class nsIPresShell;
+
+// 1295f7c0-96b3-41fc-93ed-c95dfb712ce7
+#define NS_ILAYOUT_DEBUGGER_IID \
+{ 0x1295f7c0, 0x96b3, 0x41fc, \
+ { 0x93, 0xed, 0xc9, 0x5d, 0xfb, 0x71, 0x2c, 0xe7 } }
+
+/**
+ * API for access and control of layout debugging
+ */
+class nsILayoutDebugger : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILAYOUT_DEBUGGER_IID)
+
+ NS_IMETHOD SetShowFrameBorders(bool aEnable) = 0;
+
+ NS_IMETHOD GetShowFrameBorders(bool* aResult) = 0;
+
+ NS_IMETHOD SetShowEventTargetFrameBorder(bool aEnable) = 0;
+
+ NS_IMETHOD GetShowEventTargetFrameBorder(bool* aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsILayoutDebugger, NS_ILAYOUT_DEBUGGER_IID)
+
+#endif /* nsILayoutDebugger_h___ */
diff --git a/layout/base/nsILayoutHistoryState.h b/layout/base/nsILayoutHistoryState.h
new file mode 100644
index 000000000..5562cfdd1
--- /dev/null
+++ b/layout/base/nsILayoutHistoryState.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface for container for information saved in session history when
+ * the document is not
+ */
+
+#ifndef _nsILayoutHistoryState_h
+#define _nsILayoutHistoryState_h
+
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+class nsPresState;
+template<typename> struct already_AddRefed;
+
+#define NS_ILAYOUTHISTORYSTATE_IID \
+{ 0xaef27cb3, 0x4df9, 0x4eeb, \
+ { 0xb0, 0xb0, 0xac, 0x56, 0xcf, 0x86, 0x1d, 0x04 } }
+
+class nsILayoutHistoryState : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILAYOUTHISTORYSTATE_IID)
+
+ /**
+ * Set |aState| as the state object for |aKey|.
+ * This _transfers_ownership_ of |aState| to the LayoutHistoryState.
+ * It will be freed when RemoveState() is called or when the
+ * LayoutHistoryState is destroyed.
+ */
+ virtual void AddState(const nsCString& aKey, nsPresState* aState) = 0;
+
+ /**
+ * Look up the state object for |aKey|.
+ */
+ virtual nsPresState* GetState(const nsCString& aKey) = 0;
+
+ /**
+ * Remove the state object for |aKey|.
+ */
+ virtual void RemoveState(const nsCString& aKey) = 0;
+
+ /**
+ * Check whether this history has any states in it
+ */
+ virtual bool HasStates() const = 0;
+
+ /**
+ * Sets whether this history can contain only scroll position history
+ * or all possible history
+ */
+ virtual void SetScrollPositionOnly(const bool aFlag) = 0;
+
+ /**
+ * Resets nsPresState::GetScrollState of all nsPresState objects to 0,0.
+ */
+ virtual void ResetScrollState() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsILayoutHistoryState,
+ NS_ILAYOUTHISTORYSTATE_IID)
+
+already_AddRefed<nsILayoutHistoryState>
+NS_NewLayoutHistoryState();
+
+#endif /* _nsILayoutHistoryState_h */
+
diff --git a/layout/base/nsIPercentBSizeObserver.h b/layout/base/nsIPercentBSizeObserver.h
new file mode 100644
index 000000000..cd6a9618c
--- /dev/null
+++ b/layout/base/nsIPercentBSizeObserver.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIPercentBSizeObserver_h___
+#define nsIPercentBSizeObserver_h___
+
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+struct ReflowInput;
+} // namespace mozilla
+
+/**
+ * This interface is supported by frames that need to provide computed bsize
+ * values to children during reflow which would otherwise not happen. Currently
+ * only table cells support this.
+ */
+class nsIPercentBSizeObserver
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIPercentBSizeObserver)
+
+ // Notify the observer that aReflowInput has no computed bsize,
+ // but it has a percent bsize
+ virtual void NotifyPercentBSize(const mozilla::ReflowInput& aReflowInput) = 0;
+
+ // Ask the observer if it should observe aReflowInput.frame
+ virtual bool NeedsToObserve(const mozilla::ReflowInput& aReflowInput) = 0;
+};
+
+#endif // nsIPercentBSizeObserver_h___
diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h
new file mode 100644
index 000000000..cbbae0e8f
--- /dev/null
+++ b/layout/base/nsIPresShell.h
@@ -0,0 +1,1868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 05/03/2000 IBM Corp. Observer related defines for reflow
+ */
+
+/* a presentation of a document, part 2 */
+
+#ifndef nsIPresShell_h___
+#define nsIPresShell_h___
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/WeakPtr.h"
+#include "gfxPoint.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsISupports.h"
+#include "nsIContent.h"
+#include "nsISelectionController.h"
+#include "nsQueryFrame.h"
+#include "nsCoord.h"
+#include "nsColor.h"
+#include "nsFrameManagerBase.h"
+#include "nsRect.h"
+#include "nsRegionFwd.h"
+#include "mozFlushType.h"
+#include "nsWeakReference.h"
+#include <stdio.h> // for FILE definition
+#include "nsChangeHint.h"
+#include "nsRefPtrHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsPresArena.h"
+#include "nsIImageLoadingContent.h"
+#include "nsMargin.h"
+#include "nsFrameState.h"
+#include "Units.h"
+
+#ifdef MOZ_B2G
+#include "nsIHardwareKeyHandler.h"
+#endif
+
+class nsDocShell;
+class nsIDocument;
+class nsIFrame;
+class nsPresContext;
+class nsViewManager;
+class nsView;
+class nsRenderingContext;
+class nsIPageSequenceFrame;
+class nsCanvasFrame;
+class nsAString;
+class nsCaret;
+namespace mozilla {
+class AccessibleCaretEventHub;
+class CSSStyleSheet;
+} // namespace mozilla
+class nsFrameSelection;
+class nsFrameManager;
+class nsILayoutHistoryState;
+class nsIReflowCallback;
+class nsIDOMNode;
+class nsCSSFrameConstructor;
+class nsISelection;
+template<class E> class nsCOMArray;
+class nsWeakFrame;
+class nsIScrollableFrame;
+class gfxContext;
+class nsIDOMEvent;
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsPIDOMWindowOuter;
+struct nsPoint;
+class nsINode;
+struct nsRect;
+class nsRegion;
+class nsRefreshDriver;
+class nsARefreshObserver;
+class nsAPostRefreshObserver;
+#ifdef ACCESSIBILITY
+class nsAccessibilityService;
+namespace mozilla {
+namespace a11y {
+class DocAccessible;
+} // namespace a11y
+} // namespace mozilla
+#endif
+struct nsArenaMemoryStats;
+class nsITimer;
+
+namespace mozilla {
+class EventStates;
+
+namespace dom {
+class Element;
+class Touch;
+class Selection;
+class ShadowRoot;
+} // namespace dom
+
+namespace layers {
+class LayerManager;
+} // namespace layers
+
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+} // namespace mozilla
+
+// Flags to pass to SetCapturingContent
+//
+// when assigning capture, ignore whether capture is allowed or not
+#define CAPTURE_IGNOREALLOWED 1
+// true if events should be targeted at the capturing content or its children
+#define CAPTURE_RETARGETTOELEMENT 2
+// true if the current capture wants drags to be prevented
+#define CAPTURE_PREVENTDRAG 4
+// true when the mouse is pointer locked, and events are sent to locked element
+#define CAPTURE_POINTERLOCK 8
+
+typedef struct CapturingContentInfo {
+ // capture should only be allowed during a mousedown event
+ bool mAllowed;
+ bool mPointerLock;
+ bool mRetargetToElement;
+ bool mPreventDrag;
+ mozilla::StaticRefPtr<nsIContent> mContent;
+} CapturingContentInfo;
+
+// a75573d6-34c8-4485-8fb7-edcb6fc70e12
+#define NS_IPRESSHELL_IID \
+{ 0xa75573d6, 0x34c8, 0x4485, \
+ { 0x8f, 0xb7, 0xed, 0xcb, 0x6f, 0xc7, 0x0e, 0x12 } }
+
+// debug VerifyReflow flags
+#define VERIFY_REFLOW_ON 0x01
+#define VERIFY_REFLOW_NOISY 0x02
+#define VERIFY_REFLOW_ALL 0x04
+#define VERIFY_REFLOW_DUMP_COMMANDS 0x08
+#define VERIFY_REFLOW_NOISY_RC 0x10
+#define VERIFY_REFLOW_REALLY_NOISY_RC 0x20
+#define VERIFY_REFLOW_DURING_RESIZE_REFLOW 0x40
+
+#undef NOISY_INTERRUPTIBLE_REFLOW
+
+enum nsRectVisibility {
+ nsRectVisibility_kVisible,
+ nsRectVisibility_kAboveViewport,
+ nsRectVisibility_kBelowViewport,
+ nsRectVisibility_kLeftOfViewport,
+ nsRectVisibility_kRightOfViewport
+};
+
+/**
+ * Presentation shell interface. Presentation shells are the
+ * controlling point for managing the presentation of a document. The
+ * presentation shell holds a live reference to the document, the
+ * presentation context, the style manager, the style set and the root
+ * frame. <p>
+ *
+ * When this object is Release'd, it will release the document, the
+ * presentation context, the style manager, the style set and the root
+ * frame.
+ */
+
+class nsIPresShell : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPRESSHELL_IID)
+
+protected:
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ enum eRenderFlag {
+ STATE_IGNORING_VIEWPORT_SCROLLING = 0x1,
+ STATE_DRAWWINDOW_NOT_FLUSHING = 0x2
+ };
+ typedef uint8_t RenderFlags; // for storing the above flags
+
+public:
+ /**
+ * All callers are responsible for calling |Destroy| after calling
+ * |EndObservingDocument|. It needs to be separate only because form
+ * controls incorrectly store their data in the frames rather than the
+ * content model and printing calls |EndObservingDocument| multiple
+ * times to make form controls behave nicely when printed.
+ */
+ virtual void Destroy() = 0;
+
+ bool IsDestroying() { return mIsDestroying; }
+
+ /**
+ * Make a one-way transition into a "zombie" state. In this state,
+ * no reflow is done, no painting is done, and no refresh driver
+ * ticks are processed. This is a dangerous state: it can leave
+ * areas of the composition target unpainted if callers aren't
+ * careful. (Don't let your zombie presshell out of the shed.)
+ *
+ * This is used in cases where a presshell is created for reasons
+ * other than reflow/painting.
+ */
+ virtual void MakeZombie() = 0;
+
+ /**
+ * All frames owned by the shell are allocated from an arena. They
+ * are also recycled using free lists. Separate free lists are
+ * maintained for each frame type (aID), which must always correspond
+ * to the same aSize value. AllocateFrame returns zero-filled memory.
+ * AllocateFrame is infallible and will abort on out-of-memory.
+ */
+ void* AllocateFrame(nsQueryFrame::FrameIID aID, size_t aSize)
+ {
+ void* result = mFrameArena.AllocateByFrameID(aID, aSize);
+ RecordAlloc(result);
+ memset(result, 0, aSize);
+ return result;
+ }
+
+ void FreeFrame(nsQueryFrame::FrameIID aID, void* aPtr)
+ {
+ RecordFree(aPtr);
+ if (!mIsDestroying)
+ mFrameArena.FreeByFrameID(aID, aPtr);
+ }
+
+ /**
+ * This is for allocating other types of objects (not frames). Separate free
+ * lists are maintained for each type (aID), which must always correspond to
+ * the same aSize value. AllocateByObjectID returns zero-filled memory.
+ * AllocateByObjectID is infallible and will abort on out-of-memory.
+ */
+ void* AllocateByObjectID(mozilla::ArenaObjectID aID, size_t aSize)
+ {
+ void* result = mFrameArena.AllocateByObjectID(aID, aSize);
+ RecordAlloc(result);
+ memset(result, 0, aSize);
+ return result;
+ }
+
+ void FreeByObjectID(mozilla::ArenaObjectID aID, void* aPtr)
+ {
+ RecordFree(aPtr);
+ if (!mIsDestroying)
+ mFrameArena.FreeByObjectID(aID, aPtr);
+ }
+
+ /**
+ * Other objects closely related to the frame tree that are allocated
+ * from a separate set of per-size free lists. Note that different types
+ * of objects that has the same size are allocated from the same list.
+ * AllocateMisc does *not* clear the memory that it returns.
+ * AllocateMisc is infallible and will abort on out-of-memory.
+ *
+ * @deprecated use AllocateByObjectID/FreeByObjectID instead
+ */
+ void* AllocateMisc(size_t aSize)
+ {
+ void* result = mFrameArena.AllocateBySize(aSize);
+ RecordAlloc(result);
+ return result;
+ }
+
+ void FreeMisc(size_t aSize, void* aPtr)
+ {
+ RecordFree(aPtr);
+ if (!mIsDestroying)
+ mFrameArena.FreeBySize(aSize, aPtr);
+ }
+
+ template<typename T>
+ void RegisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr)
+ {
+ mFrameArena.RegisterArenaRefPtr(aPtr);
+ }
+
+ template<typename T>
+ void DeregisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr)
+ {
+ mFrameArena.DeregisterArenaRefPtr(aPtr);
+ }
+
+ void ClearArenaRefPtrs(mozilla::ArenaObjectID aObjectID)
+ {
+ mFrameArena.ClearArenaRefPtrs(aObjectID);
+ }
+
+ nsIDocument* GetDocument() const { return mDocument; }
+
+ nsPresContext* GetPresContext() const { return mPresContext; }
+
+ nsViewManager* GetViewManager() const { return mViewManager; }
+
+ nsRefreshDriver* GetRefreshDriver() const;
+
+#ifdef ACCESSIBILITY
+ /**
+ * Return the document accessible for this pres shell if there is one.
+ */
+ mozilla::a11y::DocAccessible* GetDocAccessible() const
+ {
+ return mDocAccessible;
+ }
+
+ /**
+ * Set the document accessible for this pres shell.
+ */
+ void SetDocAccessible(mozilla::a11y::DocAccessible* aDocAccessible)
+ {
+ mDocAccessible = aDocAccessible;
+ }
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+ mozilla::StyleSetHandle StyleSet() const { return mStyleSet; }
+
+ nsCSSFrameConstructor* FrameConstructor() const { return mFrameConstructor; }
+
+ nsFrameManager* FrameManager() const {
+ // reinterpret_cast is valid since nsFrameManager does not add
+ // any members over nsFrameManagerBase.
+ return reinterpret_cast<nsFrameManager*>
+ (const_cast<nsIPresShell*>(this)->mFrameManager);
+ }
+
+#endif
+
+ /* Enable/disable author style level. Disabling author style disables the entire
+ * author level of the cascade, including the HTML preshint level.
+ */
+ // XXX these could easily be inlined, but there is a circular #include
+ // problem with nsStyleSet.
+ void SetAuthorStyleDisabled(bool aDisabled);
+ bool GetAuthorStyleDisabled() const;
+
+ /*
+ * Called when stylesheets are added/removed/enabled/disabled to
+ * recompute style and clear other cached data as needed. This will
+ * not reconstruct style synchronously; if you need to do that, call
+ * FlushPendingNotifications to flush out style reresolves.
+ *
+ * This handles the the addition and removal of the various types of
+ * style rules that can be in CSS style sheets, such as @font-face
+ * rules and @counter-style rules.
+ *
+ * It requires that StyleSheetAdded, StyleSheetRemoved,
+ * StyleSheetApplicableStateChanged, StyleRuleAdded, StyleRuleRemoved,
+ * or StyleRuleChanged has been called on the style sheets that have
+ * changed.
+ *
+ * // XXXbz why do we have this on the interface anyway? The only consumer
+ * is calling AddOverrideStyleSheet/RemoveOverrideStyleSheet, and I think
+ * those should just handle reconstructing style data...
+ */
+ void RestyleForCSSRuleChanges();
+
+ /**
+ * Update the style set somehow to take into account changed prefs which
+ * affect document styling.
+ */
+ virtual void UpdatePreferenceStyles() = 0;
+
+ /**
+ * FrameSelection will return the Frame based selection API.
+ * You cannot go back and forth anymore with QI between nsIDOM sel and
+ * nsIFrame sel.
+ */
+ already_AddRefed<nsFrameSelection> FrameSelection();
+
+ /**
+ * ConstFrameSelection returns an object which methods are safe to use for
+ * example in nsIFrame code.
+ */
+ const nsFrameSelection* ConstFrameSelection() const { return mSelection; }
+
+ // Make shell be a document observer. If called after Destroy() has
+ // been called on the shell, this will be ignored.
+ virtual void BeginObservingDocument() = 0;
+
+ // Make shell stop being a document observer
+ virtual void EndObservingDocument() = 0;
+
+ /**
+ * Return whether Initialize() was previously called.
+ */
+ bool DidInitialize() const { return mDidInitialize; }
+
+ /**
+ * Perform initialization. Constructs the frame for the root content
+ * object and then enqueues a reflow of the frame model into the
+ * specified width and height.
+ *
+ * The coordinates for aWidth and aHeight must be in standard nscoords.
+ *
+ * Callers of this method must hold a reference to this shell that
+ * is guaranteed to survive through arbitrary script execution.
+ * Calling Initialize can execute arbitrary script.
+ */
+ virtual nsresult Initialize(nscoord aWidth, nscoord aHeight) = 0;
+
+ /**
+ * Reflow the frame model into a new width and height. The
+ * coordinates for aWidth and aHeight must be in standard nscoord's.
+ */
+ virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth = 0, nscoord aOldHeight = 0) = 0;
+ /**
+ * Do the same thing as ResizeReflow but even if ResizeReflowOverride was
+ * called previously.
+ */
+ virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight) = 0;
+
+ /**
+ * Returns true if ResizeReflowOverride has been called.
+ */
+ virtual bool GetIsViewportOverridden() = 0;
+
+ /**
+ * Return true if the presshell expects layout flush.
+ */
+ virtual bool IsLayoutFlushObserver() = 0;
+
+ /**
+ * Called when document load completes.
+ */
+ virtual void LoadComplete() = 0;
+
+ /**
+ * This calls through to the frame manager to get the root frame.
+ */
+ virtual nsIFrame* GetRootFrameExternal() const;
+ nsIFrame* GetRootFrame() const {
+#ifdef MOZILLA_INTERNAL_API
+ return mFrameManager->GetRootFrame();
+#else
+ return GetRootFrameExternal();
+#endif
+ }
+
+ /*
+ * Get root scroll frame from FrameManager()->GetRootFrame().
+ */
+ nsIFrame* GetRootScrollFrame() const;
+
+ /*
+ * The same as GetRootScrollFrame, but returns an nsIScrollableFrame
+ */
+ nsIScrollableFrame* GetRootScrollFrameAsScrollable() const;
+
+ /*
+ * The same as GetRootScrollFrame, but returns an nsIScrollableFrame.
+ * Can be called by code not linked into gklayout.
+ */
+ virtual nsIScrollableFrame* GetRootScrollFrameAsScrollableExternal() const;
+
+ /*
+ * Gets nearest scrollable frame from current focused content or DOM
+ * selection if there is no focused content. The frame is scrollable with
+ * overflow:scroll or overflow:auto in some direction when aDirection is
+ * eEither. Otherwise, this returns a nearest frame that is scrollable in
+ * the specified direction.
+ */
+ enum ScrollDirection { eHorizontal, eVertical, eEither };
+ nsIScrollableFrame* GetFrameToScrollAsScrollable(ScrollDirection aDirection);
+
+ /**
+ * Returns the page sequence frame associated with the frame hierarchy.
+ * Returns nullptr if not a paginated view.
+ */
+ virtual nsIPageSequenceFrame* GetPageSequenceFrame() const = 0;
+
+ /**
+ * Returns the canvas frame associated with the frame hierarchy.
+ * Returns nullptr if is XUL document.
+ */
+ virtual nsCanvasFrame* GetCanvasFrame() const = 0;
+
+ /**
+ * Gets the placeholder frame associated with the specified frame. This is
+ * a helper frame that forwards the request to the frame manager.
+ */
+ virtual nsIFrame* GetPlaceholderFrameFor(nsIFrame* aFrame) const = 0;
+
+ /**
+ * Tell the pres shell that a frame needs to be marked dirty and needs
+ * Reflow. It's OK if this is an ancestor of the frame needing reflow as
+ * long as the ancestor chain between them doesn't cross a reflow root.
+ *
+ * The bit to add should be NS_FRAME_IS_DIRTY, NS_FRAME_HAS_DIRTY_CHILDREN
+ * or nsFrameState(0); passing 0 means that dirty bits won't be set on the
+ * frame or its ancestors/descendants, but that intrinsic widths will still
+ * be marked dirty. Passing aIntrinsicDirty = eResize and aBitToAdd = 0
+ * would result in no work being done, so don't do that.
+ */
+ enum IntrinsicDirty {
+ // XXXldb eResize should be renamed
+ eResize, // don't mark any intrinsic widths dirty
+ eTreeChange, // mark intrinsic widths dirty on aFrame and its ancestors
+ eStyleChange // Do eTreeChange, plus all of aFrame's descendants
+ };
+ enum ReflowRootHandling {
+ ePositionOrSizeChange, // aFrame is changing position or size
+ eNoPositionOrSizeChange, // ... NOT changing ...
+ eInferFromBitToAdd // is changing iff (aBitToAdd == NS_FRAME_IS_DIRTY)
+
+ // Note: With eStyleChange, these can also apply to out-of-flows
+ // in addition to aFrame.
+ };
+ virtual void FrameNeedsReflow(nsIFrame *aFrame,
+ IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd,
+ ReflowRootHandling aRootHandling =
+ eInferFromBitToAdd) = 0;
+
+ /**
+ * Calls FrameNeedsReflow on all fixed position children of the root frame.
+ */
+ virtual void MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty);
+
+ /**
+ * Tell the presshell that the given frame's reflow was interrupted. This
+ * will mark as having dirty children a path from the given frame (inclusive)
+ * to the nearest ancestor with a dirty subtree, or to the reflow root
+ * currently being reflowed if no such ancestor exists (inclusive). This is
+ * to be done immediately after reflow of the current reflow root completes.
+ * This method must only be called during reflow, and the frame it's being
+ * called on must be in the process of being reflowed when it's called. This
+ * method doesn't mark any intrinsic widths dirty and doesn't add any bits
+ * other than NS_FRAME_HAS_DIRTY_CHILDREN.
+ */
+ virtual void FrameNeedsToContinueReflow(nsIFrame *aFrame) = 0;
+
+ virtual void CancelAllPendingReflows() = 0;
+
+ virtual void NotifyCounterStylesAreDirty() = 0;
+
+ /**
+ * Destroy the frames for aContent. Note that this may destroy frames
+ * for an ancestor instead - aDestroyedFramesFor contains the content node
+ * where frames were actually destroyed (which should be used in the
+ * CreateFramesFor call). The frame tree state will be captured before
+ * the frames are destroyed in the frame constructor.
+ */
+ virtual void DestroyFramesFor(nsIContent* aContent,
+ nsIContent** aDestroyedFramesFor) = 0;
+ /**
+ * Create new frames for aContent. It will use the last captured layout
+ * history state captured in the frame constructor to restore the state
+ * in the new frame tree.
+ */
+ virtual void CreateFramesFor(nsIContent* aContent) = 0;
+
+ /**
+ * Recreates the frames for a node
+ */
+ virtual nsresult RecreateFramesFor(nsIContent* aContent) = 0;
+
+ void PostRecreateFramesFor(mozilla::dom::Element* aElement);
+ void RestyleForAnimation(mozilla::dom::Element* aElement,
+ nsRestyleHint aHint);
+
+ // ShadowRoot has APIs that can change styles so we only
+ // want to restyle elements in the ShadowRoot and not the whole
+ // document.
+ virtual void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) = 0;
+
+ /**
+ * Determine if it is safe to flush all pending notifications
+ * @param aIsSafeToFlush true if it is safe, false otherwise.
+ *
+ */
+ virtual bool IsSafeToFlush() const = 0;
+
+ /**
+ * Flush pending notifications of the type specified. This method
+ * will not affect the content model; it'll just affect style and
+ * frames. Callers that actually want up-to-date presentation (other
+ * than the document itself) should probably be calling
+ * nsIDocument::FlushPendingNotifications.
+ *
+ * @param aType the type of notifications to flush
+ */
+ virtual void FlushPendingNotifications(mozFlushType aType) = 0;
+ virtual void FlushPendingNotifications(mozilla::ChangesToFlush aType) = 0;
+
+ /**
+ * Callbacks will be called even if reflow itself fails for
+ * some reason.
+ */
+ virtual nsresult PostReflowCallback(nsIReflowCallback* aCallback) = 0;
+ virtual void CancelReflowCallback(nsIReflowCallback* aCallback) = 0;
+
+ virtual void ClearFrameRefs(nsIFrame* aFrame) = 0;
+
+ /**
+ * Get a reference rendering context. This is a context that should not
+ * be rendered to, but is suitable for measuring text and performing
+ * other non-rendering operations. Guaranteed to return non-null.
+ */
+ virtual already_AddRefed<gfxContext> CreateReferenceRenderingContext() = 0;
+
+ /**
+ * Informs the pres shell that the document is now at the anchor with
+ * the given name. If |aScroll| is true, scrolls the view of the
+ * document so that the anchor with the specified name is displayed at
+ * the top of the window. If |aAnchorName| is empty, then this informs
+ * the pres shell that there is no current target, and |aScroll| must
+ * be false. If |aAdditionalScrollFlags| is nsIPresShell::SCROLL_SMOOTH_AUTO
+ * and |aScroll| is true, the scrolling may be performed with an animation.
+ */
+ virtual nsresult GoToAnchor(const nsAString& aAnchorName, bool aScroll,
+ uint32_t aAdditionalScrollFlags = 0) = 0;
+
+ /**
+ * Tells the presshell to scroll again to the last anchor scrolled to by
+ * GoToAnchor, if any. This scroll only happens if the scroll
+ * position has not changed since the last GoToAnchor. This is called
+ * by nsDocumentViewer::LoadComplete. This clears the last anchor
+ * scrolled to by GoToAnchor (we don't want to keep it alive if it's
+ * removed from the DOM), so don't call this more than once.
+ */
+ virtual nsresult ScrollToAnchor() = 0;
+
+ enum {
+ SCROLL_TOP = 0,
+ SCROLL_BOTTOM = 100,
+ SCROLL_LEFT = 0,
+ SCROLL_RIGHT = 100,
+ SCROLL_CENTER = 50,
+ SCROLL_MINIMUM = -1
+ };
+
+ enum WhenToScroll {
+ SCROLL_ALWAYS,
+ SCROLL_IF_NOT_VISIBLE,
+ SCROLL_IF_NOT_FULLY_VISIBLE
+ };
+ typedef struct ScrollAxis {
+ int16_t mWhereToScroll;
+ WhenToScroll mWhenToScroll : 8;
+ bool mOnlyIfPerceivedScrollableDirection : 1;
+ /**
+ * @param aWhere: Either a percentage or a special value.
+ * nsIPresShell defines:
+ * * (Default) SCROLL_MINIMUM = -1: The visible area is
+ * scrolled to show the entire frame. If the frame is too
+ * large, the top and left edges are given precedence.
+ * * SCROLL_TOP = 0: The frame's upper edge is aligned with the
+ * top edge of the visible area.
+ * * SCROLL_BOTTOM = 100: The frame's bottom edge is aligned
+ * with the bottom edge of the visible area.
+ * * SCROLL_LEFT = 0: The frame's left edge is aligned with the
+ * left edge of the visible area.
+ * * SCROLL_RIGHT = 100: The frame's right edge is aligned with
+ * the right edge of the visible area.
+ * * SCROLL_CENTER = 50: The frame is centered along the axis
+ * the ScrollAxis is used for.
+ *
+ * Other values are treated as a percentage, and the point
+ * "percent" down the frame is placed at the point "percent"
+ * down the visible area.
+ * @param aWhen:
+ * * (Default) SCROLL_IF_NOT_FULLY_VISIBLE: Move the frame only
+ * if it is not fully visible (including if it's not visible
+ * at all). Note that in this case if the frame is too large to
+ * fit in view, it will only be scrolled if more of it can fit
+ * than is already in view.
+ * * SCROLL_IF_NOT_VISIBLE: Move the frame only if none of it
+ * is visible.
+ * * SCROLL_ALWAYS: Move the frame regardless of its current
+ * visibility.
+ * @param aOnlyIfPerceivedScrollableDirection:
+ * If the direction is not a perceived scrollable direction (i.e.
+ * no scrollbar showing and less than one device pixel of
+ * scrollable distance), don't scroll. Defaults to false.
+ */
+ explicit ScrollAxis(int16_t aWhere = SCROLL_MINIMUM,
+ WhenToScroll aWhen = SCROLL_IF_NOT_FULLY_VISIBLE,
+ bool aOnlyIfPerceivedScrollableDirection = false) :
+ mWhereToScroll(aWhere), mWhenToScroll(aWhen),
+ mOnlyIfPerceivedScrollableDirection(aOnlyIfPerceivedScrollableDirection)
+ {}
+ } ScrollAxis;
+ /**
+ * Scrolls the view of the document so that the primary frame of the content
+ * is displayed in the window. Layout is flushed before scrolling.
+ *
+ * @param aContent The content object of which primary frame should be
+ * scrolled into view.
+ * @param aVertical How to align the frame vertically and when to do so.
+ * This is a ScrollAxis of Where and When.
+ * @param aHorizontal How to align the frame horizontally and when to do so.
+ * This is a ScrollAxis of Where and When.
+ * @param aFlags If SCROLL_FIRST_ANCESTOR_ONLY is set, only the nearest
+ * scrollable ancestor is scrolled, otherwise all
+ * scrollable ancestors may be scrolled if necessary.
+ * If SCROLL_OVERFLOW_HIDDEN is set then we may scroll in a
+ * direction even if overflow:hidden is specified in that
+ * direction; otherwise we will not scroll in that direction
+ * when overflow:hidden is set for that direction.
+ * If SCROLL_NO_PARENT_FRAMES is set then we only scroll
+ * nodes in this document, not in any parent documents which
+ * contain this document in a iframe or the like.
+ * If SCROLL_SMOOTH is set and CSSOM-VIEW scroll-behavior
+ * is enabled, we will scroll smoothly using
+ * nsIScrollableFrame::ScrollMode::SMOOTH_MSD; otherwise,
+ * nsIScrollableFrame::ScrollMode::INSTANT will be used.
+ * If SCROLL_SMOOTH_AUTO is set, the CSSOM-View
+ * scroll-behavior attribute is set to 'smooth' on the
+ * scroll frame, and CSSOM-VIEW scroll-behavior is enabled,
+ * we will scroll smoothly using
+ * nsIScrollableFrame::ScrollMode::SMOOTH_MSD; otherwise,
+ * nsIScrollableFrame::ScrollMode::INSTANT will be used.
+ */
+ virtual nsresult ScrollContentIntoView(nsIContent* aContent,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ uint32_t aFlags) = 0;
+
+ enum {
+ SCROLL_FIRST_ANCESTOR_ONLY = 0x01,
+ SCROLL_OVERFLOW_HIDDEN = 0x02,
+ SCROLL_NO_PARENT_FRAMES = 0x04,
+ SCROLL_SMOOTH = 0x08,
+ SCROLL_SMOOTH_AUTO = 0x10
+ };
+ /**
+ * Scrolls the view of the document so that the given area of a frame
+ * is visible, if possible. Layout is not flushed before scrolling.
+ *
+ * @param aRect relative to aFrame
+ * @param aVertical see ScrollContentIntoView and ScrollAxis
+ * @param aHorizontal see ScrollContentIntoView and ScrollAxis
+ * @param aFlags if SCROLL_FIRST_ANCESTOR_ONLY is set, only the
+ * nearest scrollable ancestor is scrolled, otherwise all
+ * scrollable ancestors may be scrolled if necessary
+ * if SCROLL_OVERFLOW_HIDDEN is set then we may scroll in a direction
+ * even if overflow:hidden is specified in that direction; otherwise
+ * we will not scroll in that direction when overflow:hidden is
+ * set for that direction
+ * If SCROLL_NO_PARENT_FRAMES is set then we only scroll
+ * nodes in this document, not in any parent documents which
+ * contain this document in a iframe or the like.
+ * @return true if any scrolling happened, false if no scrolling happened
+ */
+ virtual bool ScrollFrameRectIntoView(nsIFrame* aFrame,
+ const nsRect& aRect,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ uint32_t aFlags) = 0;
+
+ /**
+ * Determine if a rectangle specified in the frame's coordinate system
+ * intersects the viewport "enough" to be considered visible.
+ * @param aFrame frame that aRect coordinates are specified relative to
+ * @param aRect rectangle in twips to test for visibility
+ * @param aMinTwips is the minimum distance in from the edge of the viewport
+ * that an object must be to be counted visible
+ * @return nsRectVisibility_kVisible if the rect is visible
+ * nsRectVisibility_kAboveViewport
+ * nsRectVisibility_kBelowViewport
+ * nsRectVisibility_kLeftOfViewport
+ * nsRectVisibility_kRightOfViewport rectangle is outside the viewport
+ * in the specified direction
+ */
+ virtual nsRectVisibility GetRectVisibility(nsIFrame *aFrame,
+ const nsRect &aRect,
+ nscoord aMinTwips) const = 0;
+
+ /**
+ * Suppress notification of the frame manager that frames are
+ * being destroyed.
+ */
+ virtual void SetIgnoreFrameDestruction(bool aIgnore) = 0;
+
+ /**
+ * Notification sent by a frame informing the pres shell that it is about to
+ * be destroyed.
+ * This allows any outstanding references to the frame to be cleaned up
+ */
+ virtual void NotifyDestroyingFrame(nsIFrame* aFrame) = 0;
+
+ /**
+ * Get the AccessibleCaretEventHub, if it exists. AddRefs it.
+ */
+ virtual already_AddRefed<mozilla::AccessibleCaretEventHub> GetAccessibleCaretEventHub() const = 0;
+
+ /**
+ * Get the caret, if it exists. AddRefs it.
+ */
+ virtual already_AddRefed<nsCaret> GetCaret() const = 0;
+
+ /**
+ * Set the current caret to a new caret. To undo this, call RestoreCaret.
+ */
+ virtual void SetCaret(nsCaret *aNewCaret) = 0;
+
+ /**
+ * Restore the caret to the original caret that this pres shell was created
+ * with.
+ */
+ virtual void RestoreCaret() = 0;
+
+ /**
+ * Should the images have borders etc. Actual visual effects are determined
+ * by the frames. Visual effects may not effect layout, only display.
+ * Takes effect on next repaint, does not force a repaint itself.
+ *
+ * @param aInEnable if true, visual selection effects are enabled
+ * if false visual selection effects are disabled
+ */
+ NS_IMETHOD SetSelectionFlags(int16_t aInEnable) = 0;
+
+ /**
+ * Gets the current state of non text selection effects
+ * @return current state of non text selection,
+ * as set by SetDisplayNonTextSelection
+ */
+ int16_t GetSelectionFlags() const { return mSelectionFlags; }
+
+ virtual mozilla::dom::Selection*
+ GetCurrentSelection(mozilla::SelectionType aSelectionType) = 0;
+
+ /**
+ * Gets a selection controller for the focused content in the DOM window
+ * for mDocument.
+ *
+ * @param aFocusedContent If there is focused content in the DOM window,
+ * the focused content will be returned. This may
+ * be nullptr if it's not necessary.
+ * @return A selection controller for focused content.
+ * E.g., if an <input> element has focus, returns
+ * the independent selection controller of it.
+ * If the DOM window does not have focused content
+ * (similar to Document.activeElement), returns
+ * nullptr.
+ */
+ virtual already_AddRefed<nsISelectionController>
+ GetSelectionControllerForFocusedContent(
+ nsIContent** aFocusedContent = nullptr) = 0;
+
+ /**
+ * Interface to dispatch events via the presshell
+ * @note The caller must have a strong reference to the PresShell.
+ */
+ virtual nsresult HandleEventWithTarget(
+ mozilla::WidgetEvent* aEvent,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsEventStatus* aStatus) = 0;
+
+ /**
+ * Dispatch event to content only (NOT full processing)
+ * @note The caller must have a strong reference to the PresShell.
+ */
+ virtual nsresult HandleDOMEventWithTarget(
+ nsIContent* aTargetContent,
+ mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus) = 0;
+
+ /**
+ * Dispatch event to content only (NOT full processing)
+ * @note The caller must have a strong reference to the PresShell.
+ */
+ virtual nsresult HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ nsIDOMEvent* aEvent,
+ nsEventStatus* aStatus) = 0;
+
+ /**
+ * Dispatch AfterKeyboardEvent with specific target.
+ */
+ virtual void DispatchAfterKeyboardEvent(nsINode* aTarget,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled) = 0;
+
+ /**
+ * Return whether or not the event is valid to be dispatched
+ */
+ virtual bool CanDispatchEvent(
+ const mozilla::WidgetGUIEvent* aEvent = nullptr) const = 0;
+
+ /**
+ * Gets the current target event frame from the PresShell
+ */
+ virtual nsIFrame* GetEventTargetFrame() = 0;
+
+ /**
+ * Gets the current target event frame from the PresShell
+ */
+ virtual already_AddRefed<nsIContent> GetEventTargetContent(
+ mozilla::WidgetEvent* aEvent) = 0;
+
+ /**
+ * Get and set the history state for the current document
+ */
+
+ virtual nsresult CaptureHistoryState(nsILayoutHistoryState** aLayoutHistoryState) = 0;
+
+ /**
+ * Determine if reflow is currently locked
+ * returns true if reflow is locked, false otherwise
+ */
+ bool IsReflowLocked() const { return mIsReflowing; }
+
+ /**
+ * Called to find out if painting is suppressed for this presshell. If it is suppressd,
+ * we don't allow the painting of any layer but the background, and we don't
+ * recur into our children.
+ */
+ bool IsPaintingSuppressed() const { return mPaintingSuppressed; }
+
+ /**
+ * Pause painting by freezing the refresh driver of this and all parent
+ * presentations. This may not have the desired effect if this pres shell
+ * has its own refresh driver.
+ */
+ virtual void PausePainting() = 0;
+
+ /**
+ * Resume painting by thawing the refresh driver of this and all parent
+ * presentations. This may not have the desired effect if this pres shell
+ * has its own refresh driver.
+ */
+ virtual void ResumePainting() = 0;
+
+ /**
+ * Unsuppress painting.
+ */
+ virtual void UnsuppressPainting() = 0;
+
+ /**
+ * Called to disable nsITheme support in a specific presshell.
+ */
+ void DisableThemeSupport()
+ {
+ // Doesn't have to be dynamic. Just set the bool.
+ mIsThemeSupportDisabled = true;
+ }
+
+ /**
+ * Indicates whether theme support is enabled.
+ */
+ bool IsThemeSupportEnabled() const { return !mIsThemeSupportDisabled; }
+
+ /**
+ * Get the set of agent style sheets for this presentation
+ */
+ virtual nsresult GetAgentStyleSheets(
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets) = 0;
+
+ /**
+ * Replace the set of agent style sheets
+ */
+ virtual nsresult SetAgentStyleSheets(
+ const nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets) = 0;
+
+ /**
+ * Add an override style sheet for this presentation
+ */
+ virtual nsresult AddOverrideStyleSheet(mozilla::StyleSheet* aSheet) = 0;
+
+ /**
+ * Remove an override style sheet
+ */
+ virtual nsresult RemoveOverrideStyleSheet(mozilla::StyleSheet* aSheet) = 0;
+
+ /**
+ * Reconstruct frames for all elements in the document
+ */
+ virtual nsresult ReconstructFrames() = 0;
+
+ /**
+ * Notify that a content node's state has changed
+ */
+ virtual void ContentStateChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ mozilla::EventStates aStateMask) = 0;
+
+ /**
+ * See if reflow verification is enabled. To enable reflow verification add
+ * "verifyreflow:1" to your MOZ_LOG environment variable (any non-zero
+ * debug level will work). Or, call SetVerifyReflowEnable with true.
+ */
+ static bool GetVerifyReflowEnable();
+
+ /**
+ * Set the verify-reflow enable flag.
+ */
+ static void SetVerifyReflowEnable(bool aEnabled);
+
+ virtual nsIFrame* GetAbsoluteContainingBlock(nsIFrame* aFrame);
+
+#ifdef MOZ_REFLOW_PERF
+ virtual void DumpReflows() = 0;
+ virtual void CountReflows(const char * aName, nsIFrame * aFrame) = 0;
+ virtual void PaintCount(const char * aName,
+ nsRenderingContext* aRenderingContext,
+ nsPresContext * aPresContext,
+ nsIFrame * aFrame,
+ const nsPoint& aOffset,
+ uint32_t aColor) = 0;
+ virtual void SetPaintFrameCount(bool aOn) = 0;
+ virtual bool IsPaintingFrameCounts() = 0;
+#endif
+
+#ifdef DEBUG
+ // Debugging hooks
+ virtual void ListStyleContexts(nsIFrame *aRootFrame, FILE *out,
+ int32_t aIndent = 0) = 0;
+
+ virtual void ListStyleSheets(FILE *out, int32_t aIndent = 0) = 0;
+ virtual void VerifyStyleTree() = 0;
+#endif
+
+#ifdef ACCESSIBILITY
+ /**
+ * Return true if accessibility is active.
+ */
+ static bool IsAccessibilityActive();
+
+ /**
+ * Return accessibility service if accessibility is active.
+ */
+ static nsAccessibilityService* AccService();
+#endif
+
+ /**
+ * Stop all active elements (plugins and the caret) in this presentation and
+ * in the presentations of subdocuments. Resets painting to a suppressed state.
+ * XXX this should include image animations
+ */
+ virtual void Freeze() = 0;
+ bool IsFrozen() { return mFrozen; }
+
+ /**
+ * Restarts active elements (plugins) in this presentation and in the
+ * presentations of subdocuments, then do a full invalidate of the content area.
+ */
+ virtual void Thaw() = 0;
+
+ virtual void FireOrClearDelayedEvents(bool aFireEvents) = 0;
+
+ /**
+ * When this shell is disconnected from its containing docshell, we
+ * lose our container pointer. However, we'd still like to be able to target
+ * user events at the docshell's parent. This pointer allows us to do that.
+ * It should not be used for any other purpose.
+ */
+ void SetForwardingContainer(const mozilla::WeakPtr<nsDocShell> &aContainer);
+
+ /**
+ * Render the document into an arbitrary gfxContext
+ * Designed for getting a picture of a document or a piece of a document
+ * Note that callers will generally want to call FlushPendingNotifications
+ * to get an up-to-date view of the document
+ * @param aRect is the region to capture into the offscreen buffer, in the
+ * root frame's coordinate system (if aIgnoreViewportScrolling is false)
+ * or in the root scrolled frame's coordinate system
+ * (if aIgnoreViewportScrolling is true). The coordinates are in appunits.
+ * @param aFlags see below;
+ * set RENDER_IS_UNTRUSTED if the contents may be passed to malicious
+ * agents. E.g. we might choose not to paint the contents of sensitive widgets
+ * such as the file name in a file upload widget, and we might choose not
+ * to paint themes.
+ * set RENDER_IGNORE_VIEWPORT_SCROLLING to ignore
+ * clipping and scrollbar painting due to scrolling in the viewport
+ * set RENDER_CARET to draw the caret if one would be visible
+ * (by default the caret is never drawn)
+ * set RENDER_USE_LAYER_MANAGER to force rendering to go through
+ * the layer manager for the window. This may be unexpectedly slow
+ * (if the layer manager must read back data from the GPU) or low-quality
+ * (if the layer manager reads back pixel data and scales it
+ * instead of rendering using the appropriate scaling). It may also
+ * slow everything down if the area rendered does not correspond to the
+ * normal visible area of the window.
+ * set RENDER_ASYNC_DECODE_IMAGES to avoid having images synchronously
+ * decoded during rendering.
+ * (by default images decode synchronously with RenderDocument)
+ * set RENDER_DOCUMENT_RELATIVE to render the document as if there has been
+ * no scrolling and interpret |aRect| relative to the document instead of the
+ * CSS viewport. Only considered if RENDER_IGNORE_VIEWPORT_SCROLLING is set
+ * or the document is in ignore viewport scrolling mode
+ * (nsIPresShell::SetIgnoreViewportScrolling/IgnoringViewportScrolling).
+ * @param aBackgroundColor a background color to render onto
+ * @param aRenderedContext the gfxContext to render to. We render so that
+ * one CSS pixel in the source document is rendered to one unit in the current
+ * transform.
+ */
+ enum {
+ RENDER_IS_UNTRUSTED = 0x01,
+ RENDER_IGNORE_VIEWPORT_SCROLLING = 0x02,
+ RENDER_CARET = 0x04,
+ RENDER_USE_WIDGET_LAYERS = 0x08,
+ RENDER_ASYNC_DECODE_IMAGES = 0x10,
+ RENDER_DOCUMENT_RELATIVE = 0x20,
+ RENDER_DRAWWINDOW_NOT_FLUSHING = 0x40
+ };
+ virtual nsresult RenderDocument(const nsRect& aRect, uint32_t aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aRenderedContext) = 0;
+
+ enum {
+ RENDER_IS_IMAGE = 0x100,
+ RENDER_AUTO_SCALE = 0x80
+ };
+
+ /**
+ * Renders a node aNode to a surface and returns it. The aRegion may be used
+ * to clip the rendering. This region is measured in CSS pixels from the
+ * edge of the presshell area. The aPoint, aScreenRect and aFlags arguments
+ * function in a similar manner as RenderSelection.
+ */
+ virtual already_AddRefed<mozilla::gfx::SourceSurface>
+ RenderNode(nsIDOMNode* aNode,
+ nsIntRegion* aRegion,
+ const mozilla::LayoutDeviceIntPoint aPoint,
+ mozilla::LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags) = 0;
+
+ /**
+ * Renders a selection to a surface and returns it. This method is primarily
+ * intended to create the drag feedback when dragging a selection.
+ *
+ * aScreenRect will be filled in with the bounding rectangle of the
+ * selection area on screen.
+ *
+ * If the area of the selection is large and the RENDER_AUTO_SCALE flag is
+ * set, the image will be scaled down. The argument aPoint is used in this
+ * case as a reference point when determining the new screen rectangle after
+ * scaling. Typically, this will be the mouse position, so that the screen
+ * rectangle is positioned such that the mouse is over the same point in the
+ * scaled image as in the original. When scaling does not occur, the mouse
+ * point isn't used because the position can be determined from the displayed
+ * frames.
+ */
+ virtual already_AddRefed<mozilla::gfx::SourceSurface>
+ RenderSelection(nsISelection* aSelection,
+ const mozilla::LayoutDeviceIntPoint aPoint,
+ mozilla::LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags) = 0;
+
+ void AddWeakFrameInternal(nsWeakFrame* aWeakFrame);
+ virtual void AddWeakFrameExternal(nsWeakFrame* aWeakFrame);
+
+ void AddWeakFrame(nsWeakFrame* aWeakFrame)
+ {
+#ifdef MOZILLA_INTERNAL_API
+ AddWeakFrameInternal(aWeakFrame);
+#else
+ AddWeakFrameExternal(aWeakFrame);
+#endif
+ }
+
+ void RemoveWeakFrameInternal(nsWeakFrame* aWeakFrame);
+ virtual void RemoveWeakFrameExternal(nsWeakFrame* aWeakFrame);
+
+ void RemoveWeakFrame(nsWeakFrame* aWeakFrame)
+ {
+#ifdef MOZILLA_INTERNAL_API
+ RemoveWeakFrameInternal(aWeakFrame);
+#else
+ RemoveWeakFrameExternal(aWeakFrame);
+#endif
+ }
+
+#ifdef DEBUG
+ nsIFrame* GetDrawEventTargetFrame() { return mDrawEventTargetFrame; }
+#endif
+
+ /**
+ * Stop or restart non synthetic test mouse event handling on *all*
+ * presShells.
+ *
+ * @param aDisable If true, disable all non synthetic test mouse
+ * events on all presShells. Otherwise, enable them.
+ */
+ virtual void DisableNonTestMouseEvents(bool aDisable) = 0;
+
+ /**
+ * Record the background color of the most recently drawn canvas. This color
+ * is composited on top of the user's default background color and then used
+ * to draw the background color of the canvas. See PresShell::Paint,
+ * PresShell::PaintDefaultBackground, and nsDocShell::SetupNewViewer;
+ * bug 488242, bug 476557 and other bugs mentioned there.
+ */
+ void SetCanvasBackground(nscolor aColor) { mCanvasBackgroundColor = aColor; }
+ nscolor GetCanvasBackground() { return mCanvasBackgroundColor; }
+
+ /**
+ * Use the current frame tree (if it exists) to update the background
+ * color of the most recently drawn canvas.
+ */
+ virtual void UpdateCanvasBackground() = 0;
+
+ /**
+ * Add a solid color item to the bottom of aList with frame aFrame and bounds
+ * aBounds. Checks first if this needs to be done by checking if aFrame is a
+ * canvas frame (if the FORCE_DRAW flag is passed then this check is skipped).
+ * aBackstopColor is composed behind the background color of the canvas, it is
+ * transparent by default.
+ */
+ enum {
+ FORCE_DRAW = 0x01
+ };
+ virtual void AddCanvasBackgroundColorItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds,
+ nscolor aBackstopColor = NS_RGBA(0,0,0,0),
+ uint32_t aFlags = 0) = 0;
+
+
+ /**
+ * Add a solid color item to the bottom of aList with frame aFrame and
+ * bounds aBounds representing the dark grey background behind the page of a
+ * print preview presentation.
+ */
+ virtual void AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds) = 0;
+
+ /**
+ * Computes the backstop color for the view: transparent if in a transparent
+ * widget, otherwise the PresContext default background color. This color is
+ * only visible if the contents of the view as a whole are translucent.
+ */
+ virtual nscolor ComputeBackstopColor(nsView* aDisplayRoot) = 0;
+
+ void ObserveNativeAnonMutationsForPrint(bool aObserve)
+ {
+ mObservesMutationsForPrint = aObserve;
+ }
+ bool ObservesNativeAnonMutationsForPrint()
+ {
+ return mObservesMutationsForPrint;
+ }
+
+ virtual nsresult SetIsActive(bool aIsActive) = 0;
+
+ bool IsActive()
+ {
+ return mIsActive;
+ }
+
+ // mouse capturing
+ static CapturingContentInfo gCaptureInfo;
+
+ class PointerCaptureInfo final
+ {
+ public:
+ nsCOMPtr<nsIContent> mPendingContent;
+ nsCOMPtr<nsIContent> mOverrideContent;
+
+ explicit PointerCaptureInfo(nsIContent* aPendingContent)
+ : mPendingContent(aPendingContent)
+ {
+ MOZ_COUNT_CTOR(PointerCaptureInfo);
+ }
+
+ ~PointerCaptureInfo()
+ {
+ MOZ_COUNT_DTOR(PointerCaptureInfo);
+ }
+
+ bool Empty()
+ {
+ return !(mPendingContent || mOverrideContent);
+ }
+ };
+
+ class PointerInfo final
+ {
+ public:
+ uint16_t mPointerType;
+ bool mActiveState;
+ bool mPrimaryState;
+ explicit PointerInfo(bool aActiveState, uint16_t aPointerType,
+ bool aPrimaryState)
+ : mPointerType(aPointerType)
+ , mActiveState(aActiveState)
+ , mPrimaryState(aPrimaryState)
+ {
+ }
+ };
+
+ static void DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture,
+ uint32_t aPointerId,
+ uint16_t aPointerType,
+ bool aIsPrimary,
+ nsIContent* aCaptureTarget);
+ static PointerCaptureInfo* GetPointerCaptureInfo(uint32_t aPointerId);
+ static void SetPointerCapturingContent(uint32_t aPointerId,
+ nsIContent* aContent);
+ static void ReleasePointerCapturingContent(uint32_t aPointerId);
+ static nsIContent* GetPointerCapturingContent(uint32_t aPointerId);
+
+ // CheckPointerCaptureState checks cases, when got/lostpointercapture events
+ // should be fired.
+ static void CheckPointerCaptureState(uint32_t aPointerId,
+ uint16_t aPointerType, bool aIsPrimary);
+
+ // GetPointerInfo returns true if pointer with aPointerId is situated in
+ // device, false otherwise.
+ // aActiveState is additional information, which shows state of pointer like
+ // button state for mouse.
+ static bool GetPointerInfo(uint32_t aPointerId, bool& aActiveState);
+
+ // GetPointerType returns pointer type like mouse, pen or touch for pointer
+ // event with pointerId
+ static uint16_t GetPointerType(uint32_t aPointerId);
+
+ // GetPointerPrimaryState returns state of attribute isPrimary for pointer
+ // event with pointerId
+ static bool GetPointerPrimaryState(uint32_t aPointerId);
+
+ /**
+ * When capturing content is set, it traps all mouse events and retargets
+ * them at this content node. If capturing is not allowed
+ * (gCaptureInfo.mAllowed is false), then capturing is not set. However, if
+ * the CAPTURE_IGNOREALLOWED flag is set, the allowed state is ignored and
+ * capturing is set regardless. To disable capture, pass null for the value
+ * of aContent.
+ *
+ * If CAPTURE_RETARGETTOELEMENT is set, all mouse events are targeted at
+ * aContent only. Otherwise, mouse events are targeted at aContent or its
+ * descendants. That is, descendants of aContent receive mouse events as
+ * they normally would, but mouse events outside of aContent are retargeted
+ * to aContent.
+ *
+ * If CAPTURE_PREVENTDRAG is set then drags are prevented from starting while
+ * this capture is active.
+ *
+ * If CAPTURE_POINTERLOCK is set, similar to CAPTURE_RETARGETTOELEMENT, then
+ * events are targeted at aContent, but capturing is held more strongly (i.e.,
+ * calls to SetCapturingContent won't unlock unless CAPTURE_POINTERLOCK is
+ * set again).
+ */
+ static void SetCapturingContent(nsIContent* aContent, uint8_t aFlags);
+
+ /**
+ * Return the active content currently capturing the mouse if any.
+ */
+ static nsIContent* GetCapturingContent()
+ {
+ return gCaptureInfo.mContent;
+ }
+
+ /**
+ * Allow or disallow mouse capturing.
+ */
+ static void AllowMouseCapture(bool aAllowed)
+ {
+ gCaptureInfo.mAllowed = aAllowed;
+ }
+
+ /**
+ * Returns true if there is an active mouse capture that wants to prevent
+ * drags.
+ */
+ static bool IsMouseCapturePreventingDrag()
+ {
+ return gCaptureInfo.mPreventDrag && gCaptureInfo.mContent;
+ }
+
+ /**
+ * Keep track of how many times this presshell has been rendered to
+ * a window.
+ */
+ uint64_t GetPaintCount() { return mPaintCount; }
+ void IncrementPaintCount() { ++mPaintCount; }
+
+ /**
+ * Get the root DOM window of this presShell.
+ */
+ virtual already_AddRefed<nsPIDOMWindowOuter> GetRootWindow() = 0;
+
+ /**
+ * Get the layer manager for the widget of the root view, if it has
+ * one.
+ */
+ virtual LayerManager* GetLayerManager() = 0;
+
+ /**
+ * Return true iff there is a widget rendering this presShell and that
+ * widget is APZ-enabled.
+ */
+ virtual bool AsyncPanZoomEnabled() = 0;
+
+ /**
+ * Track whether we're ignoring viewport scrolling for the purposes
+ * of painting. If we are ignoring, then layers aren't clipped to
+ * the CSS viewport and scrollbars aren't drawn.
+ */
+ virtual void SetIgnoreViewportScrolling(bool aIgnore) = 0;
+ bool IgnoringViewportScrolling() const
+ { return mRenderFlags & STATE_IGNORING_VIEWPORT_SCROLLING; }
+
+ /**
+ * Set a "resolution" for the document, which if not 1.0 will
+ * allocate more or fewer pixels for rescalable content by a factor
+ * of |resolution| in both dimensions. Return NS_OK iff the
+ * resolution bounds are sane, and the resolution of this was
+ * actually updated.
+ *
+ * The resolution defaults to 1.0.
+ */
+ virtual nsresult SetResolution(float aResolution) = 0;
+ float GetResolution() const { return mResolution.valueOr(1.0); }
+ virtual float GetCumulativeResolution() = 0;
+
+ /**
+ * Calculate the cumulative scale resolution from this document up to
+ * but not including the root document.
+ */
+ virtual float GetCumulativeNonRootScaleResolution() = 0;
+
+ /**
+ * Was the current resolution set by the user or just default initialized?
+ */
+ bool IsResolutionSet() { return mResolution.isSome(); }
+
+ /**
+ * Similar to SetResolution() but also increases the scale of the content
+ * by the same amount.
+ */
+ virtual nsresult SetResolutionAndScaleTo(float aResolution) = 0;
+
+ /**
+ * Return whether we are scaling to the set resolution.
+ * This is initially false; it's set to true by a call to
+ * SetResolutionAndScaleTo(), and set to false by a call to SetResolution().
+ */
+ virtual bool ScaleToResolution() const = 0;
+
+ /**
+ * Used by session restore code to restore a resolution before the first
+ * paint.
+ */
+ virtual void SetRestoreResolution(float aResolution,
+ mozilla::LayoutDeviceIntSize aDisplaySize) = 0;
+
+ /**
+ * Returns whether we are in a DrawWindow() call that used the
+ * DRAWWINDOW_DO_NOT_FLUSH flag.
+ */
+ bool InDrawWindowNotFlushing() const
+ { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
+
+ /**
+ * Set the isFirstPaint flag.
+ */
+ void SetIsFirstPaint(bool aIsFirstPaint) { mIsFirstPaint = aIsFirstPaint; }
+
+ /**
+ * Get the isFirstPaint flag.
+ */
+ bool GetIsFirstPaint() const { return mIsFirstPaint; }
+
+ uint32_t GetPresShellId() { return mPresShellId; }
+
+ /**
+ * Dispatch a mouse move event based on the most recent mouse position if
+ * this PresShell is visible. This is used when the contents of the page
+ * moved (aFromScroll is false) or scrolled (aFromScroll is true).
+ */
+ virtual void SynthesizeMouseMove(bool aFromScroll) = 0;
+
+ enum PaintFlags {
+ /* Update the layer tree and paint PaintedLayers. If this is not specified,
+ * we may still have to do it if the layer tree lost PaintedLayer contents
+ * we need for compositing. */
+ PAINT_LAYERS = 0x01,
+ /* Composite layers to the window. */
+ PAINT_COMPOSITE = 0x02,
+ /* Sync-decode images. */
+ PAINT_SYNC_DECODE_IMAGES = 0x04
+ };
+ virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
+ uint32_t aFlags) = 0;
+ virtual nsresult HandleEvent(nsIFrame* aFrame,
+ mozilla::WidgetGUIEvent* aEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus,
+ nsIContent** aTargetContent = nullptr) = 0;
+ virtual bool ShouldIgnoreInvalidation() = 0;
+ /**
+ * Notify that we're going to call Paint with PAINT_LAYERS
+ * on the pres shell for a widget (which might not be this one, since
+ * WillPaint is called on all presshells in the same toplevel window as the
+ * painted widget). This is issued at a time when it's safe to modify
+ * widget geometry.
+ */
+ virtual void WillPaint() = 0;
+ /**
+ * Notify that we're going to call Paint with PAINT_COMPOSITE.
+ * Fires on the presshell for the painted widget.
+ * This is issued at a time when it's safe to modify widget geometry.
+ */
+ virtual void WillPaintWindow() = 0;
+ /**
+ * Notify that we called Paint with PAINT_COMPOSITE.
+ * Fires on the presshell for the painted widget.
+ * This is issued at a time when it's safe to modify widget geometry.
+ */
+ virtual void DidPaintWindow() = 0;
+
+ /**
+ * Ensures that the refresh driver is running, and schedules a view
+ * manager flush on the next tick.
+ *
+ * @param aType PAINT_DELAYED_COMPRESS : Schedule a paint to be executed after a delay, and
+ * put FrameLayerBuilder in 'compressed' mode that avoids short cut optimizations.
+ */
+ enum PaintType {
+ PAINT_DEFAULT,
+ PAINT_DELAYED_COMPRESS
+ };
+ virtual void ScheduleViewManagerFlush(PaintType aType = PAINT_DEFAULT) = 0;
+ virtual void ClearMouseCaptureOnView(nsView* aView) = 0;
+ virtual bool IsVisible() = 0;
+ virtual void DispatchSynthMouseMove(mozilla::WidgetGUIEvent* aEvent,
+ bool aFlushOnHoverChange) = 0;
+
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ nsArenaMemoryStats *aArenaObjectsSize,
+ size_t *aPresShellSize,
+ size_t *aStyleSetsSize,
+ size_t *aTextRunsSize,
+ size_t *aPresContextSize) = 0;
+
+ /**
+ * Methods that retrieve the cached font inflation preferences.
+ */
+ uint32_t FontSizeInflationEmPerLine() const {
+ return mFontSizeInflationEmPerLine;
+ }
+
+ uint32_t FontSizeInflationMinTwips() const {
+ return mFontSizeInflationMinTwips;
+ }
+
+ uint32_t FontSizeInflationLineThreshold() const {
+ return mFontSizeInflationLineThreshold;
+ }
+
+ bool FontSizeInflationForceEnabled() const {
+ return mFontSizeInflationForceEnabled;
+ }
+
+ bool FontSizeInflationDisabledInMasterProcess() const {
+ return mFontSizeInflationDisabledInMasterProcess;
+ }
+
+ /**
+ * Determine if font size inflation is enabled. This value is cached until
+ * it becomes dirty.
+ *
+ * @returns true, if font size inflation is enabled; false otherwise.
+ */
+ bool FontSizeInflationEnabled();
+
+ /**
+ * Notify the pres shell that an event occurred making the current value of
+ * mFontSizeInflationEnabled invalid. This will schedule a recomputation of
+ * whether font size inflation is enabled on the next call to
+ * FontSizeInflationEnabled().
+ */
+ void NotifyFontSizeInflationEnabledIsDirty()
+ {
+ mFontSizeInflationEnabledIsDirty = true;
+ }
+
+ virtual void AddInvalidateHiddenPresShellObserver(nsRefreshDriver *aDriver) = 0;
+
+ void InvalidatePresShellIfHidden();
+ void CancelInvalidatePresShellIfHidden();
+
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Approximate frame visibility tracking public API.
+ //////////////////////////////////////////////////////////////////////////////
+
+ /// Schedule an update of the list of approximately visible frames "soon".
+ /// This lets the refresh driver know that we want a visibility update in the
+ /// near future. The refresh driver applies its own heuristics and throttling
+ /// to decide when to actually perform the visibility update.
+ virtual void ScheduleApproximateFrameVisibilityUpdateSoon() = 0;
+
+ /// Schedule an update of the list of approximately visible frames "now". The
+ /// update runs asynchronously, but it will be posted to the event loop
+ /// immediately. Prefer the "soon" variation of this method when possible, as
+ /// this variation ignores the refresh driver's heuristics.
+ virtual void ScheduleApproximateFrameVisibilityUpdateNow() = 0;
+
+ /// Clears the current list of approximately visible frames on this pres shell
+ /// and replaces it with frames that are in the display list @aList.
+ virtual void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) = 0;
+ virtual void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr,
+ bool aRemoveOnly = false) = 0;
+
+ /// Ensures @aFrame is in the list of approximately visible frames.
+ virtual void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) = 0;
+
+ /// Removes @aFrame from the list of approximately visible frames if present.
+ virtual void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) = 0;
+
+ /// Whether we should assume all frames are visible.
+ virtual bool AssumeAllFramesVisible() = 0;
+
+
+ /**
+ * Returns whether the document's style set's rule processor for the
+ * specified level of the cascade is shared by multiple style sets.
+ *
+ * @param aSheetType One of the nsIStyleSheetService.*_SHEET constants.
+ */
+ nsresult HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal);
+
+ /**
+ * Refresh observer management.
+ */
+protected:
+ virtual bool AddRefreshObserverExternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType);
+ bool AddRefreshObserverInternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType);
+ virtual bool RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType);
+ bool RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType);
+
+ /**
+ * Do computations necessary to determine if font size inflation is enabled.
+ * This value is cached after computation, as the computation is somewhat
+ * expensive.
+ */
+ void RecomputeFontSizeInflationEnabled();
+
+ void RecordAlloc(void* aPtr) {
+#ifdef DEBUG
+ MOZ_ASSERT(!mAllocatedPointers.Contains(aPtr));
+ mAllocatedPointers.PutEntry(aPtr);
+#endif
+ }
+
+ void RecordFree(void* aPtr) {
+#ifdef DEBUG
+ MOZ_ASSERT(mAllocatedPointers.Contains(aPtr));
+ mAllocatedPointers.RemoveEntry(aPtr);
+#endif
+ }
+
+public:
+ bool AddRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType) {
+#ifdef MOZILLA_INTERNAL_API
+ return AddRefreshObserverInternal(aObserver, aFlushType);
+#else
+ return AddRefreshObserverExternal(aObserver, aFlushType);
+#endif
+ }
+
+ bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType) {
+#ifdef MOZILLA_INTERNAL_API
+ return RemoveRefreshObserverInternal(aObserver, aFlushType);
+#else
+ return RemoveRefreshObserverExternal(aObserver, aFlushType);
+#endif
+ }
+
+ virtual bool AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
+ virtual bool RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
+
+ /**
+ * Initialize and shut down static variables.
+ */
+ static void InitializeStatics();
+ static void ReleaseStatics();
+
+ // If a frame in the subtree rooted at aFrame is capturing the mouse then
+ // clears that capture.
+ static void ClearMouseCapture(nsIFrame* aFrame);
+
+ void SetScrollPositionClampingScrollPortSize(nscoord aWidth, nscoord aHeight);
+ bool IsScrollPositionClampingScrollPortSizeSet() {
+ return mScrollPositionClampingScrollPortSizeSet;
+ }
+ nsSize GetScrollPositionClampingScrollPortSize() {
+ NS_ASSERTION(mScrollPositionClampingScrollPortSizeSet, "asking for scroll port when its not set?");
+ return mScrollPositionClampingScrollPortSize;
+ }
+
+ virtual void WindowSizeMoveDone() = 0;
+ virtual void SysColorChanged() = 0;
+ virtual void ThemeChanged() = 0;
+ virtual void BackingScaleFactorChanged() = 0;
+
+ /**
+ * Documents belonging to an invisible DocShell must not be painted ever.
+ */
+ bool IsNeverPainting() {
+ return mIsNeverPainting;
+ }
+
+ void SetNeverPainting(bool aNeverPainting) {
+ mIsNeverPainting = aNeverPainting;
+ }
+
+ bool HasPendingReflow() const
+ { return mReflowScheduled || mReflowContinueTimer; }
+
+ void SyncWindowProperties(nsView* aView);
+
+#ifdef ANDROID
+ virtual nsIDocument* GetTouchEventTargetDocument() = 0;
+#endif
+
+protected:
+ friend class nsRefreshDriver;
+
+ // IMPORTANT: The ownership implicit in the following member variables
+ // has been explicitly checked. If you add any members to this class,
+ // please make the ownership explicit (pinkerton, scc).
+
+ // These are the same Document and PresContext owned by the DocViewer.
+ // we must share ownership.
+ nsCOMPtr<nsIDocument> mDocument;
+ RefPtr<nsPresContext> mPresContext;
+ mozilla::StyleSetHandle mStyleSet; // [OWNS]
+ nsCSSFrameConstructor* mFrameConstructor; // [OWNS]
+ nsViewManager* mViewManager; // [WEAK] docViewer owns it so I don't have to
+ nsPresArena mFrameArena;
+ RefPtr<nsFrameSelection> mSelection;
+ // Pointer into mFrameConstructor - this is purely so that FrameManager() and
+ // GetRootFrame() can be inlined:
+ nsFrameManagerBase* mFrameManager;
+ mozilla::WeakPtr<nsDocShell> mForwardingContainer;
+ nsRefreshDriver* MOZ_UNSAFE_REF("These two objects hold weak references "
+ "to each other, and the validity of this "
+ "member is ensured by the logic in nsIPresShell.")
+ mHiddenInvalidationObserverRefreshDriver;
+#ifdef ACCESSIBILITY
+ mozilla::a11y::DocAccessible* mDocAccessible;
+#endif
+
+ // At least on Win32 and Mac after interupting a reflow we need to post
+ // the resume reflow event off a timer to avoid event starvation because
+ // posted messages are processed before other messages when the modal
+ // moving/sizing loop is running, see bug 491700 for details.
+ nsCOMPtr<nsITimer> mReflowContinueTimer;
+
+#ifdef MOZ_B2G
+ // Forward hardware key events to the input-method-app
+ nsCOMPtr<nsIHardwareKeyHandler> mHardwareKeyHandler;
+#endif // MOZ_B2G
+
+#ifdef DEBUG
+ nsIFrame* mDrawEventTargetFrame;
+
+ // We track allocated pointers in a debug-only hashtable to assert against
+ // missing/double frees.
+ nsTHashtable<nsPtrHashKey<void>> mAllocatedPointers;
+#endif
+
+
+ // Count of the number of times this presshell has been painted to a window.
+ uint64_t mPaintCount;
+
+ nsSize mScrollPositionClampingScrollPortSize;
+
+ // A list of weak frames. This is a pointer to the last item in the list.
+ nsWeakFrame* mWeakFrames;
+
+ // Most recent canvas background color.
+ nscolor mCanvasBackgroundColor;
+
+ // Used to force allocation and rendering of proportionally more or
+ // less pixels in both dimensions.
+ mozilla::Maybe<float> mResolution;
+
+ int16_t mSelectionFlags;
+
+ // Flags controlling how our document is rendered. These persist
+ // between paints and so are tied with retained layer pixels.
+ // PresShell flushes retained layers when the rendering state
+ // changes in a way that prevents us from being able to (usefully)
+ // re-use old pixels.
+ RenderFlags mRenderFlags;
+
+ // Indicates that the whole document must be restyled. Changes to scoped
+ // style sheets are recorded in mChangedScopeStyleRoots rather than here
+ // in mStylesHaveChanged.
+ bool mStylesHaveChanged : 1;
+ bool mDidInitialize : 1;
+ bool mIsDestroying : 1;
+ bool mIsZombie : 1;
+ bool mIsReflowing : 1;
+
+ // For all documents we initially lock down painting.
+ bool mPaintingSuppressed : 1;
+
+ // Whether or not form controls should use nsITheme in this shell.
+ bool mIsThemeSupportDisabled : 1;
+
+ bool mIsActive : 1;
+ bool mFrozen : 1;
+ bool mIsFirstPaint : 1;
+ bool mObservesMutationsForPrint : 1;
+
+ // If true, we have a reflow scheduled. Guaranteed to be false if
+ // mReflowContinueTimer is non-null.
+ bool mReflowScheduled : 1;
+
+ bool mSuppressInterruptibleReflows : 1;
+ bool mScrollPositionClampingScrollPortSizeSet : 1;
+
+ uint32_t mPresShellId;
+
+ // List of subtrees rooted at style scope roots that need to be restyled.
+ // When a change to a scoped style sheet is made, we add the style scope
+ // root to this array rather than setting mStylesHaveChanged = true, since
+ // we know we don't need to restyle the whole document. However, if in the
+ // same update block we have already had other changes that require
+ // the whole document to be restyled (i.e., mStylesHaveChanged is already
+ // true), then we don't bother adding the scope root here.
+ AutoTArray<RefPtr<mozilla::dom::Element>,1> mChangedScopeStyleRoots;
+
+ static nsIContent* gKeyDownTarget;
+
+ // Cached font inflation values. This is done to prevent changing of font
+ // inflation until a page is reloaded.
+ uint32_t mFontSizeInflationEmPerLine;
+ uint32_t mFontSizeInflationMinTwips;
+ uint32_t mFontSizeInflationLineThreshold;
+ bool mFontSizeInflationForceEnabled;
+ bool mFontSizeInflationDisabledInMasterProcess;
+ bool mFontSizeInflationEnabled;
+ bool mPaintingIsFrozen;
+
+ // Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
+ bool mFontSizeInflationEnabledIsDirty;
+
+ // If a document belongs to an invisible DocShell, this flag must be set
+ // to true, so we can avoid any paint calls for widget related to this
+ // presshell.
+ bool mIsNeverPainting;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIPresShell, NS_IPRESSHELL_IID)
+
+#endif /* nsIPresShell_h___ */
diff --git a/layout/base/nsIReflowCallback.h b/layout/base/nsIReflowCallback.h
new file mode 100644
index 000000000..e447b218d
--- /dev/null
+++ b/layout/base/nsIReflowCallback.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIReflowCallback_h___
+#define nsIReflowCallback_h___
+
+/**
+ * Reflow callback interface.
+ * These are not refcounted. Objects must be removed from the presshell
+ * callback list before they die.
+ * Protocol: objects will either get a ReflowFinished() call when a reflow
+ * has finished or a ReflowCallbackCanceled() call if the shell is destroyed,
+ * whichever happens first. If the object is explicitly removed from the shell
+ * (using nsIPresShell::CancelReflowCallback()) before that occurs then neither
+ * of the callback methods are called.
+ */
+class nsIReflowCallback {
+public:
+ /**
+ * The presshell calls this when reflow has finished. Return true if
+ * you need a Flush_Layout to happen after this.
+ */
+ virtual bool ReflowFinished() = 0;
+ /**
+ * The presshell calls this on outstanding callback requests in its
+ * Destroy() method. The shell removes the request after calling
+ * ReflowCallbackCanceled().
+ */
+ virtual void ReflowCallbackCanceled() = 0;
+};
+
+#endif /* nsIReflowCallback_h___ */
diff --git a/layout/base/nsIStyleSheetService.idl b/layout/base/nsIStyleSheetService.idl
new file mode 100644
index 000000000..e8d834dd3
--- /dev/null
+++ b/layout/base/nsIStyleSheetService.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* interface for managing user and user-agent style sheets */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIDOMStyleSheet;
+
+/*
+ * nsIStyleSheetService allows extensions or embeddors to add to the
+ * built-in list of user or agent style sheets.
+ */
+
+[scriptable, uuid(4de68896-e8eb-41de-8237-a797b570ac4a)]
+interface nsIStyleSheetService : nsISupports
+{
+ const unsigned long AGENT_SHEET = 0;
+ const unsigned long USER_SHEET = 1;
+ const unsigned long AUTHOR_SHEET = 2;
+
+ /**
+ * Synchronously loads a style sheet from |sheetURI| and adds it to the list
+ * of user or agent style sheets.
+ *
+ * A user sheet loaded via this API will come before userContent.css and
+ * userChrome.css in the cascade (so the rules in it will have lower
+ * precedence than rules in those sheets).
+ *
+ * An agent sheet loaded via this API will come after ua.css in the cascade
+ * (so the rules in it will have higher precedence than rules in ua.css).
+ *
+ * The relative ordering of two user or two agent sheets loaded via
+ * this API is undefined.
+ *
+ * Sheets added via this API take effect on all documents, including
+ * already-loaded ones, immediately.
+ */
+ void loadAndRegisterSheet(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Returns true if a style sheet at |sheetURI| has previously been
+ * added to the list of style sheets specified by |type|.
+ */
+ boolean sheetRegistered(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Synchronously loads a style sheet from |sheetURI| and returns the
+ * new style sheet object. Can be used with nsIDOMWindowUtils.addSheet.
+ */
+ nsIDOMStyleSheet preloadSheet(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Remove the style sheet at |sheetURI| from the list of style sheets
+ * specified by |type|. The removal takes effect immediately, even for
+ * already-loaded documents.
+ */
+ void unregisterSheet(in nsIURI sheetURI, in unsigned long type);
+};
diff --git a/layout/base/nsLayoutDebugger.cpp b/layout/base/nsLayoutDebugger.cpp
new file mode 100644
index 000000000..22c313c72
--- /dev/null
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * implementation of interface that allows layout-debug extension access
+ * to some internals of layout
+ */
+
+#include "nsILayoutDebugger.h"
+
+#include "nsAttrValue.h"
+#include "nsFrame.h"
+#include "nsDisplayList.h"
+#include "FrameLayerBuilder.h"
+#include "nsPrintfCString.h"
+#include "DisplayItemScrollClip.h"
+
+#include <iostream>
+#include <stdio.h>
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+#ifdef DEBUG
+class nsLayoutDebugger : public nsILayoutDebugger {
+public:
+ nsLayoutDebugger();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD SetShowFrameBorders(bool aEnable) override;
+
+ NS_IMETHOD GetShowFrameBorders(bool* aResult) override;
+
+ NS_IMETHOD SetShowEventTargetFrameBorder(bool aEnable) override;
+
+ NS_IMETHOD GetShowEventTargetFrameBorder(bool* aResult) override;
+
+protected:
+ virtual ~nsLayoutDebugger();
+};
+
+nsresult
+NS_NewLayoutDebugger(nsILayoutDebugger** aResult)
+{
+ NS_PRECONDITION(aResult, "null OUT ptr");
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsLayoutDebugger* it = new nsLayoutDebugger();
+ return it->QueryInterface(NS_GET_IID(nsILayoutDebugger), (void**)aResult);
+}
+
+nsLayoutDebugger::nsLayoutDebugger()
+{
+}
+
+nsLayoutDebugger::~nsLayoutDebugger()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsLayoutDebugger, nsILayoutDebugger)
+
+NS_IMETHODIMP
+nsLayoutDebugger::SetShowFrameBorders(bool aEnable)
+{
+ nsFrame::ShowFrameBorders(aEnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebugger::GetShowFrameBorders(bool* aResult)
+{
+ *aResult = nsFrame::GetShowFrameBorders();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebugger::SetShowEventTargetFrameBorder(bool aEnable)
+{
+ nsFrame::ShowEventTargetFrameBorder(aEnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebugger::GetShowEventTargetFrameBorder(bool* aResult)
+{
+ *aResult = nsFrame::GetShowEventTargetFrameBorder();
+ return NS_OK;
+}
+
+#endif
+
+std::ostream& operator<<(std::ostream& os, const nsPrintfCString& rhs) {
+ os << rhs.get();
+ return os;
+}
+
+static void
+PrintDisplayListTo(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList,
+ std::stringstream& aStream, uint32_t aIndent, bool aDumpHtml);
+
+static void
+PrintDisplayItemTo(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
+ std::stringstream& aStream, uint32_t aIndent, bool aDumpSublist, bool aDumpHtml)
+{
+ std::stringstream ss;
+
+ if (!aDumpHtml) {
+ for (uint32_t indent = 0; indent < aIndent; indent++) {
+ aStream << " ";
+ }
+ }
+ nsAutoString contentData;
+ nsIFrame* f = aItem->Frame();
+#ifdef DEBUG_FRAME_DUMP
+ f->GetFrameName(contentData);
+#endif
+ nsIContent* content = f->GetContent();
+ if (content) {
+ nsString tmp;
+ if (content->GetID()) {
+ content->GetID()->ToString(tmp);
+ contentData.AppendLiteral(" id:");
+ contentData.Append(tmp);
+ }
+ if (content->GetClasses()) {
+ content->GetClasses()->ToString(tmp);
+ contentData.AppendLiteral(" class:");
+ contentData.Append(tmp);
+ }
+ }
+ bool snap;
+ nsRect rect = aItem->GetBounds(aBuilder, &snap);
+ nsRect layerRect = rect - (*aItem->GetAnimatedGeometryRoot())->GetOffsetToCrossDoc(aItem->ReferenceFrame());
+ nsRect vis = aItem->GetVisibleRect();
+ nsRect component = aItem->GetComponentAlphaBounds(aBuilder);
+ nsDisplayList* list = aItem->GetChildren();
+ const DisplayItemClip& clip = aItem->GetClip();
+ nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ aStream << nsPrintfCString("<a href=\"javascript:ViewImage('%s')\">", string.BeginReading());
+ }
+#endif
+
+ aStream << nsPrintfCString("%s p=0x%p f=0x%p(%s) %sbounds(%d,%d,%d,%d) layerBounds(%d,%d,%d,%d) visible(%d,%d,%d,%d) componentAlpha(%d,%d,%d,%d) clip(%s) scrollClip(%s)%s ref=0x%p agr=0x%p",
+ aItem->Name(), aItem, (void*)f, NS_ConvertUTF16toUTF8(contentData).get(),
+ (aItem->ZIndex() ? nsPrintfCString("z=%d ", aItem->ZIndex()).get() : ""),
+ rect.x, rect.y, rect.width, rect.height,
+ layerRect.x, layerRect.y, layerRect.width, layerRect.height,
+ vis.x, vis.y, vis.width, vis.height,
+ component.x, component.y, component.width, component.height,
+ clip.ToString().get(),
+ DisplayItemScrollClip::ToString(aItem->ScrollClip()).get(),
+ aItem->IsUniform(aBuilder) ? " uniform" : "",
+ aItem->ReferenceFrame(), aItem->GetAnimatedGeometryRoot()->mFrame);
+
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& r = iter.Get();
+ aStream << nsPrintfCString(" (opaque %d,%d,%d,%d)", r.x, r.y, r.width, r.height);
+ }
+
+ if (aItem->Frame()->StyleDisplay()->mWillChange.Length() > 0) {
+ aStream << " (will-change=";
+ for (size_t i = 0; i < aItem->Frame()->StyleDisplay()->mWillChange.Length(); i++) {
+ if (i > 0) {
+ aStream << ",";
+ }
+ aStream << NS_LossyConvertUTF16toASCII(aItem->Frame()->StyleDisplay()->mWillChange[i]).get();
+ }
+ aStream << ")";
+ }
+
+ // Display item specific debug info
+ aItem->WriteDebugInfo(aStream);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ aStream << "</a>";
+ }
+#endif
+ uint32_t key = aItem->GetPerFrameKey();
+ Layer* layer = mozilla::FrameLayerBuilder::GetDebugOldLayerFor(f, key);
+ if (layer) {
+ if (aDumpHtml) {
+ aStream << nsPrintfCString(" <a href=\"#%p\">layer=%p</a>", layer, layer);
+ } else {
+ aStream << nsPrintfCString(" layer=0x%p", layer);
+ }
+ }
+#ifdef MOZ_DUMP_PAINTING
+ if (aItem->GetType() == nsDisplayItem::TYPE_MASK) {
+ nsCString str;
+ (static_cast<nsDisplayMask*>(aItem))->PrintEffects(str);
+ aStream << str.get();
+ }
+
+ if (aItem->GetType() == nsDisplayItem::TYPE_FILTER) {
+ nsCString str;
+ (static_cast<nsDisplayFilter*>(aItem))->PrintEffects(str);
+ aStream << str.get();
+ }
+#endif
+ aStream << "\n";
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ aStream << nsPrintfCString("<br><img id=\"%s\">\n", string.BeginReading());
+ }
+#endif
+
+ if (aDumpSublist && list) {
+ PrintDisplayListTo(aBuilder, *list, aStream, aIndent+1, aDumpHtml);
+ }
+}
+
+static void
+PrintDisplayListTo(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList,
+ std::stringstream& aStream, uint32_t aIndent, bool aDumpHtml)
+{
+ if (aDumpHtml) {
+ aStream << "<ul>";
+ }
+
+ for (nsDisplayItem* i = aList.GetBottom(); i != nullptr; i = i->GetAbove()) {
+ if (aDumpHtml) {
+ aStream << "<li>";
+ }
+ PrintDisplayItemTo(aBuilder, i, aStream, aIndent, true, aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</li>";
+ }
+ }
+
+ if (aDumpHtml) {
+ aStream << "</ul>";
+ }
+}
+
+void
+nsFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ std::stringstream& aStream,
+ bool aDumpHtml)
+{
+ PrintDisplayListTo(aBuilder, aList, aStream, 0, aDumpHtml);
+}
+
+/**
+ * The two functions below are intended to be called from a debugger.
+ */
+void
+PrintDisplayItemToStdout(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem)
+{
+ std::stringstream stream;
+ PrintDisplayItemTo(aBuilder, aItem, stream, 0, true, false);
+ std::cout << stream.str() << std::endl;
+}
+
+void
+PrintDisplayListToStdout(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList)
+{
+ std::stringstream stream;
+ PrintDisplayListTo(aBuilder, aList, stream, 0, false);
+ std::cout << stream.str() << std::endl;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void
+PrintDisplayListSetItem(nsDisplayListBuilder* aBuilder,
+ const char* aItemName,
+ const nsDisplayList& aList,
+ std::stringstream& aStream,
+ bool aDumpHtml)
+{
+ if (aDumpHtml) {
+ aStream << "<li>";
+ }
+ aStream << aItemName << "\n";
+ PrintDisplayListTo(aBuilder, aList, aStream, 0, aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</li>";
+ }
+}
+
+void
+nsFrame::PrintDisplayListSet(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aSet,
+ std::stringstream& aStream,
+ bool aDumpHtml)
+{
+ if (aDumpHtml) {
+ aStream << "<ul>";
+ }
+ PrintDisplayListSetItem(aBuilder, "[BorderBackground]", *(aSet.BorderBackground()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[BlockBorderBackgrounds]", *(aSet.BlockBorderBackgrounds()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Floats]", *(aSet.Floats()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[PositionedDescendants]", *(aSet.PositionedDescendants()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Outlines]", *(aSet.Outlines()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Content]", *(aSet.Content()), aStream, aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</ul>";
+ }
+}
+
+#endif
diff --git a/layout/base/nsLayoutHistoryState.cpp b/layout/base/nsLayoutHistoryState.cpp
new file mode 100644
index 000000000..066eeb058
--- /dev/null
+++ b/layout/base/nsLayoutHistoryState.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * container for information saved in session history when the document
+ * is not
+ */
+
+#include "nsILayoutHistoryState.h"
+#include "nsWeakReference.h"
+#include "nsClassHashtable.h"
+#include "nsPresState.h"
+#include "mozilla/Attributes.h"
+
+class nsLayoutHistoryState final : public nsILayoutHistoryState,
+ public nsSupportsWeakReference
+{
+public:
+ nsLayoutHistoryState()
+ : mScrollPositionOnly(false)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+
+ // nsILayoutHistoryState
+ virtual void
+ AddState(const nsCString& aKey, nsPresState* aState) override;
+ virtual nsPresState*
+ GetState(const nsCString& aKey) override;
+ virtual void
+ RemoveState(const nsCString& aKey) override;
+ virtual bool
+ HasStates() const override;
+ virtual void
+ SetScrollPositionOnly(const bool aFlag) override;
+ virtual void
+ ResetScrollState() override;
+
+private:
+ ~nsLayoutHistoryState() {}
+ bool mScrollPositionOnly;
+
+ nsClassHashtable<nsCStringHashKey,nsPresState> mStates;
+};
+
+
+already_AddRefed<nsILayoutHistoryState>
+NS_NewLayoutHistoryState()
+{
+ RefPtr<nsLayoutHistoryState> state = new nsLayoutHistoryState();
+ return state.forget();
+}
+
+NS_IMPL_ISUPPORTS(nsLayoutHistoryState,
+ nsILayoutHistoryState,
+ nsISupportsWeakReference)
+
+void
+nsLayoutHistoryState::AddState(const nsCString& aStateKey, nsPresState* aState)
+{
+ mStates.Put(aStateKey, aState);
+}
+
+nsPresState*
+nsLayoutHistoryState::GetState(const nsCString& aKey)
+{
+ nsPresState* state = nullptr;
+ bool entryExists = mStates.Get(aKey, &state);
+
+ if (entryExists && mScrollPositionOnly) {
+ // Ensure any state that shouldn't be restored is removed
+ state->ClearNonScrollState();
+ }
+
+ return state;
+}
+
+void
+nsLayoutHistoryState::RemoveState(const nsCString& aKey)
+{
+ mStates.Remove(aKey);
+}
+
+bool
+nsLayoutHistoryState::HasStates() const
+{
+ return mStates.Count() != 0;
+}
+
+void
+nsLayoutHistoryState::SetScrollPositionOnly(const bool aFlag)
+{
+ mScrollPositionOnly = aFlag;
+}
+
+void
+nsLayoutHistoryState::ResetScrollState()
+{
+ for (auto iter = mStates.Iter(); !iter.Done(); iter.Next()) {
+ nsPresState* state = iter.UserData();
+ if (state) {
+ state->SetScrollState(nsPoint(0, 0));
+ }
+ }
+}
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
new file mode 100644
index 000000000..ed34f39ce
--- /dev/null
+++ b/layout/base/nsLayoutUtils.cpp
@@ -0,0 +1,9186 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=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/. */
+
+#include "nsLayoutUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/layers/PAPZ.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Unused.h"
+#include "nsCharTraits.h"
+#include "nsFontMetrics.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsFrameList.h"
+#include "nsGkAtoms.h"
+#include "nsHtml5Atoms.h"
+#include "nsIAtom.h"
+#include "nsCaret.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSColorUtils.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsPlaceholderFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsIDOMEvent.h"
+#include "nsDisplayList.h"
+#include "nsRegion.h"
+#include "nsFrameManager.h"
+#include "nsBlockFrame.h"
+#include "nsBidiPresUtils.h"
+#include "imgIContainer.h"
+#include "ImageOps.h"
+#include "ImageRegion.h"
+#include "gfxRect.h"
+#include "gfxContext.h"
+#include "nsRenderingContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsCSSRendering.h"
+#include "nsTextFragment.h"
+#include "nsThemeConstants.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIWidget.h"
+#include "gfxMatrix.h"
+#include "gfxPrefs.h"
+#include "gfxTypes.h"
+#include "nsTArray.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "nsICanvasRenderingContextInternal.h"
+#include "gfxPlatform.h"
+#include <algorithm>
+#include <limits>
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "imgIRequest.h"
+#include "nsIImageLoadingContent.h"
+#include "nsCOMPtr.h"
+#include "nsCSSProps.h"
+#include "nsListControlFrame.h"
+#include "mozilla/dom/Element.h"
+#include "nsCanvasFrame.h"
+#include "gfxDrawable.h"
+#include "gfxEnv.h"
+#include "gfxUtils.h"
+#include "nsDataHashtable.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTextFrame.h"
+#include "nsFontFaceList.h"
+#include "nsFontInflationData.h"
+#include "nsSVGUtils.h"
+#include "SVGImageContext.h"
+#include "SVGTextFrame.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsIFrameInlines.h"
+#include "ImageContainer.h"
+#include "nsComputedDOMStyle.h"
+#include "ActiveLayerTracker.h"
+#include "mozilla/gfx/2D.h"
+#include "gfx2DGlue.h"
+#include "mozilla/LookAndFeel.h"
+#include "UnitTransforms.h"
+#include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE
+#include "ClientLayerManager.h"
+#include "nsRefreshDriver.h"
+#include "nsIContentViewer.h"
+#include "LayersLogging.h"
+#include "mozilla/Preferences.h"
+#include "nsFrameSelection.h"
+#include "FrameLayerBuilder.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "RegionBuilder.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPopupManager.h"
+#endif
+
+#include "GeckoProfiler.h"
+#include "nsAnimationManager.h"
+#include "nsTransitionManager.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "LayoutLogging.h"
+
+// Make sure getpid() works.
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+using namespace mozilla::gfx;
+
+#define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled"
+#define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled"
+#define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit"
+#define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled"
+#define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled"
+#define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled"
+#define BG_CLIP_TEXT_ENABLED_PREF_NAME "layout.css.background-clip-text.enabled"
+
+// The time in number of frames that we estimate for a refresh driver
+// to be quiescent
+#define DEFAULT_QUIESCENT_FRAMES 2
+// The time (milliseconds) we estimate is needed between the end of an
+// idle time and the next Tick.
+#define DEFAULT_IDLE_PERIOD_TIME_LIMIT 1.0f
+
+#ifdef DEBUG
+// TODO: remove, see bug 598468.
+bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
+#endif // DEBUG
+
+typedef FrameMetrics::ViewID ViewID;
+typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
+
+/* static */ uint32_t nsLayoutUtils::sFontSizeInflationEmPerLine;
+/* static */ uint32_t nsLayoutUtils::sFontSizeInflationMinTwips;
+/* static */ uint32_t nsLayoutUtils::sFontSizeInflationLineThreshold;
+/* static */ int32_t nsLayoutUtils::sFontSizeInflationMappingIntercept;
+/* static */ uint32_t nsLayoutUtils::sFontSizeInflationMaxRatio;
+/* static */ bool nsLayoutUtils::sFontSizeInflationForceEnabled;
+/* static */ bool nsLayoutUtils::sFontSizeInflationDisabledInMasterProcess;
+/* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled;
+/* static */ bool nsLayoutUtils::sCSSVariablesEnabled;
+/* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled;
+/* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled;
+/* static */ bool nsLayoutUtils::sTextCombineUprightDigitsEnabled;
+#ifdef MOZ_STYLO
+/* static */ bool nsLayoutUtils::sStyloEnabled;
+#endif
+/* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit;
+/* static */ uint32_t nsLayoutUtils::sQuiescentFramesBeforeIdlePeriod;
+
+static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID;
+
+typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap;
+static ContentMap* sContentMap = nullptr;
+static ContentMap& GetContentMap() {
+ if (!sContentMap) {
+ sContentMap = new ContentMap();
+ }
+ return *sContentMap;
+}
+
+// When the pref "layout.css.grid.enabled" changes, this function is invoked
+// to let us update kDisplayKTable, to selectively disable or restore the
+// entries for "grid" and "inline-grid" in that table.
+static void
+GridEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+ MOZ_ASSERT(strncmp(aPrefName, GRID_ENABLED_PREF_NAME,
+ ArrayLength(GRID_ENABLED_PREF_NAME)) == 0,
+ "We only registered this callback for a single pref, so it "
+ "should only be called for that pref");
+
+ static int32_t sIndexOfGridInDisplayTable;
+ static int32_t sIndexOfInlineGridInDisplayTable;
+ static bool sAreGridKeywordIndicesInitialized; // initialized to false
+
+ bool isGridEnabled =
+ Preferences::GetBool(GRID_ENABLED_PREF_NAME, false);
+ if (!sAreGridKeywordIndicesInitialized) {
+ // First run: find the position of "grid" and "inline-grid" in
+ // kDisplayKTable.
+ sIndexOfGridInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_grid,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfGridInDisplayTable >= 0,
+ "Couldn't find grid in kDisplayKTable");
+ sIndexOfInlineGridInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_grid,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfInlineGridInDisplayTable >= 0,
+ "Couldn't find inline-grid in kDisplayKTable");
+ sAreGridKeywordIndicesInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the "grid" entries in kDisplayKTable,
+ // depending on whether the grid pref is enabled vs. disabled.
+ if (sIndexOfGridInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfGridInDisplayTable].mKeyword =
+ isGridEnabled ? eCSSKeyword_grid : eCSSKeyword_UNKNOWN;
+ }
+ if (sIndexOfInlineGridInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfInlineGridInDisplayTable].mKeyword =
+ isGridEnabled ? eCSSKeyword_inline_grid : eCSSKeyword_UNKNOWN;
+ }
+}
+
+// When the pref "layout.css.prefixes.webkit" changes, this function is invoked
+// to let us update kDisplayKTable, to selectively disable or restore the
+// entries for "-webkit-box" and "-webkit-inline-box" in that table.
+static void
+WebkitPrefixEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+ MOZ_ASSERT(strncmp(aPrefName, WEBKIT_PREFIXES_ENABLED_PREF_NAME,
+ ArrayLength(WEBKIT_PREFIXES_ENABLED_PREF_NAME)) == 0,
+ "We only registered this callback for a single pref, so it "
+ "should only be called for that pref");
+
+ static int32_t sIndexOfWebkitBoxInDisplayTable;
+ static int32_t sIndexOfWebkitInlineBoxInDisplayTable;
+ static int32_t sIndexOfWebkitFlexInDisplayTable;
+ static int32_t sIndexOfWebkitInlineFlexInDisplayTable;
+
+ static bool sAreKeywordIndicesInitialized; // initialized to false
+
+ bool isWebkitPrefixSupportEnabled =
+ Preferences::GetBool(WEBKIT_PREFIXES_ENABLED_PREF_NAME, false);
+ if (!sAreKeywordIndicesInitialized) {
+ // First run: find the position of the keywords in kDisplayKTable.
+ sIndexOfWebkitBoxInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_box,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfWebkitBoxInDisplayTable >= 0,
+ "Couldn't find -webkit-box in kDisplayKTable");
+ sIndexOfWebkitInlineBoxInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_inline_box,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfWebkitInlineBoxInDisplayTable >= 0,
+ "Couldn't find -webkit-inline-box in kDisplayKTable");
+
+ sIndexOfWebkitFlexInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_flex,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfWebkitFlexInDisplayTable >= 0,
+ "Couldn't find -webkit-flex in kDisplayKTable");
+ sIndexOfWebkitInlineFlexInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_inline_flex,
+ nsCSSProps::kDisplayKTable);
+ MOZ_ASSERT(sIndexOfWebkitInlineFlexInDisplayTable >= 0,
+ "Couldn't find -webkit-inline-flex in kDisplayKTable");
+ sAreKeywordIndicesInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the "-webkit-{box|flex}" entries in
+ // kDisplayKTable, depending on whether the webkit prefix pref is enabled
+ // vs. disabled.
+ if (sIndexOfWebkitBoxInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfWebkitBoxInDisplayTable].mKeyword =
+ isWebkitPrefixSupportEnabled ?
+ eCSSKeyword__webkit_box : eCSSKeyword_UNKNOWN;
+ }
+ if (sIndexOfWebkitInlineBoxInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfWebkitInlineBoxInDisplayTable].mKeyword =
+ isWebkitPrefixSupportEnabled ?
+ eCSSKeyword__webkit_inline_box : eCSSKeyword_UNKNOWN;
+ }
+ if (sIndexOfWebkitFlexInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfWebkitFlexInDisplayTable].mKeyword =
+ isWebkitPrefixSupportEnabled ?
+ eCSSKeyword__webkit_flex : eCSSKeyword_UNKNOWN;
+ }
+ if (sIndexOfWebkitInlineFlexInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfWebkitInlineFlexInDisplayTable].mKeyword =
+ isWebkitPrefixSupportEnabled ?
+ eCSSKeyword__webkit_inline_flex : eCSSKeyword_UNKNOWN;
+ }
+}
+
+// When the pref "layout.css.display-contents.enabled" changes, this function is
+// invoked to let us update kDisplayKTable, to selectively disable or restore
+// the entries for "contents" in that table.
+static void
+DisplayContentsEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+ NS_ASSERTION(strcmp(aPrefName, DISPLAY_CONTENTS_ENABLED_PREF_NAME) == 0,
+ "Did you misspell " DISPLAY_CONTENTS_ENABLED_PREF_NAME " ?");
+
+ static bool sIsDisplayContentsKeywordIndexInitialized;
+ static int32_t sIndexOfContentsInDisplayTable;
+ bool isDisplayContentsEnabled =
+ Preferences::GetBool(DISPLAY_CONTENTS_ENABLED_PREF_NAME, false);
+
+ if (!sIsDisplayContentsKeywordIndexInitialized) {
+ // First run: find the position of "contents" in kDisplayKTable.
+ sIndexOfContentsInDisplayTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_contents,
+ nsCSSProps::kDisplayKTable);
+ sIsDisplayContentsKeywordIndexInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the "contents" entry in kDisplayKTable,
+ // depending on whether the pref is enabled vs. disabled.
+ if (sIndexOfContentsInDisplayTable >= 0) {
+ nsCSSProps::kDisplayKTable[sIndexOfContentsInDisplayTable].mKeyword =
+ isDisplayContentsEnabled ? eCSSKeyword_contents : eCSSKeyword_UNKNOWN;
+ }
+}
+
+// When the pref "layout.css.text-align-unsafe-value.enabled" changes, this
+// function is called to let us update kTextAlignKTable & kTextAlignLastKTable,
+// to selectively disable or restore the entries for "unsafe" in those tables.
+static void
+TextAlignUnsafeEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+ NS_ASSERTION(strcmp(aPrefName, TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME) == 0,
+ "Did you misspell " TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME " ?");
+
+ static bool sIsInitialized;
+ static int32_t sIndexOfUnsafeInTextAlignTable;
+ static int32_t sIndexOfUnsafeInTextAlignLastTable;
+ bool isTextAlignUnsafeEnabled =
+ Preferences::GetBool(TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME, false);
+
+ if (!sIsInitialized) {
+ // First run: find the position of "unsafe" in kTextAlignKTable.
+ sIndexOfUnsafeInTextAlignTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_unsafe,
+ nsCSSProps::kTextAlignKTable);
+ // First run: find the position of "unsafe" in kTextAlignLastKTable.
+ sIndexOfUnsafeInTextAlignLastTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_unsafe,
+ nsCSSProps::kTextAlignLastKTable);
+ sIsInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the "unsafe" entry in the keyword tables,
+ // depending on whether the pref is enabled vs. disabled.
+ MOZ_ASSERT(sIndexOfUnsafeInTextAlignTable >= 0);
+ nsCSSProps::kTextAlignKTable[sIndexOfUnsafeInTextAlignTable].mKeyword =
+ isTextAlignUnsafeEnabled ? eCSSKeyword_unsafe : eCSSKeyword_UNKNOWN;
+ MOZ_ASSERT(sIndexOfUnsafeInTextAlignLastTable >= 0);
+ nsCSSProps::kTextAlignLastKTable[sIndexOfUnsafeInTextAlignLastTable].mKeyword =
+ isTextAlignUnsafeEnabled ? eCSSKeyword_unsafe : eCSSKeyword_UNKNOWN;
+}
+
+// When the pref "layout.css.float-logical-values.enabled" changes, this
+// function is called to let us update kFloatKTable & kClearKTable,
+// to selectively disable or restore the entries for logical values
+// (inline-start and inline-end) in those tables.
+static void
+FloatLogicalValuesEnabledPrefChangeCallback(const char* aPrefName,
+ void* aClosure)
+{
+ NS_ASSERTION(strcmp(aPrefName, FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME) == 0,
+ "Did you misspell " FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME " ?");
+
+ static bool sIsInitialized;
+ static int32_t sIndexOfInlineStartInFloatTable;
+ static int32_t sIndexOfInlineEndInFloatTable;
+ static int32_t sIndexOfInlineStartInClearTable;
+ static int32_t sIndexOfInlineEndInClearTable;
+ bool isFloatLogicalValuesEnabled =
+ Preferences::GetBool(FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME, false);
+
+ if (!sIsInitialized) {
+ // First run: find the position of "inline-start" in kFloatKTable.
+ sIndexOfInlineStartInFloatTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_start,
+ nsCSSProps::kFloatKTable);
+ // First run: find the position of "inline-end" in kFloatKTable.
+ sIndexOfInlineEndInFloatTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_end,
+ nsCSSProps::kFloatKTable);
+ // First run: find the position of "inline-start" in kClearKTable.
+ sIndexOfInlineStartInClearTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_start,
+ nsCSSProps::kClearKTable);
+ // First run: find the position of "inline-end" in kClearKTable.
+ sIndexOfInlineEndInClearTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_end,
+ nsCSSProps::kClearKTable);
+ sIsInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the logical entries in the keyword tables,
+ // depending on whether the pref is enabled vs. disabled.
+ MOZ_ASSERT(sIndexOfInlineStartInFloatTable >= 0);
+ nsCSSProps::kFloatKTable[sIndexOfInlineStartInFloatTable].mKeyword =
+ isFloatLogicalValuesEnabled ? eCSSKeyword_inline_start : eCSSKeyword_UNKNOWN;
+ MOZ_ASSERT(sIndexOfInlineEndInFloatTable >= 0);
+ nsCSSProps::kFloatKTable[sIndexOfInlineEndInFloatTable].mKeyword =
+ isFloatLogicalValuesEnabled ? eCSSKeyword_inline_end : eCSSKeyword_UNKNOWN;
+ MOZ_ASSERT(sIndexOfInlineStartInClearTable >= 0);
+ nsCSSProps::kClearKTable[sIndexOfInlineStartInClearTable].mKeyword =
+ isFloatLogicalValuesEnabled ? eCSSKeyword_inline_start : eCSSKeyword_UNKNOWN;
+ MOZ_ASSERT(sIndexOfInlineEndInClearTable >= 0);
+ nsCSSProps::kClearKTable[sIndexOfInlineEndInClearTable].mKeyword =
+ isFloatLogicalValuesEnabled ? eCSSKeyword_inline_end : eCSSKeyword_UNKNOWN;
+}
+
+
+// When the pref "layout.css.background-clip-text.enabled" changes, this
+// function is invoked to let us update kBackgroundClipKTable, to selectively
+// disable or restore the entries for "text" in that table.
+static void
+BackgroundClipTextEnabledPrefChangeCallback(const char* aPrefName,
+ void* aClosure)
+{
+ NS_ASSERTION(strcmp(aPrefName, BG_CLIP_TEXT_ENABLED_PREF_NAME) == 0,
+ "Did you misspell " BG_CLIP_TEXT_ENABLED_PREF_NAME " ?");
+
+ static bool sIsBGClipKeywordIndexInitialized;
+ static int32_t sIndexOfTextInBGClipTable;
+ bool isBGClipTextEnabled =
+ Preferences::GetBool(BG_CLIP_TEXT_ENABLED_PREF_NAME, false);
+
+ if (!sIsBGClipKeywordIndexInitialized) {
+ // First run: find the position of "text" in kBackgroundClipKTable.
+ sIndexOfTextInBGClipTable =
+ nsCSSProps::FindIndexOfKeyword(eCSSKeyword_text,
+ nsCSSProps::kBackgroundClipKTable);
+
+ sIsBGClipKeywordIndexInitialized = true;
+ }
+
+ // OK -- now, stomp on or restore the "text" entry in kBackgroundClipKTable,
+ // depending on whether the pref is enabled vs. disabled.
+ if (sIndexOfTextInBGClipTable >= 0) {
+ nsCSSProps::kBackgroundClipKTable[sIndexOfTextInBGClipTable].mKeyword =
+ isBGClipTextEnabled ? eCSSKeyword_text : eCSSKeyword_UNKNOWN;
+ }
+}
+
+template<typename TestType>
+static bool
+HasMatchingAnimations(const nsIFrame* aFrame, TestType&& aTest)
+{
+ EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+ if (!effects) {
+ return false;
+ }
+
+ for (KeyframeEffectReadOnly* effect : *effects) {
+ if (aTest(*effect)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsLayoutUtils::HasCurrentTransitions(const nsIFrame* aFrame)
+{
+ return HasMatchingAnimations(aFrame,
+ [](KeyframeEffectReadOnly& aEffect)
+ {
+ // Since |aEffect| is current, it must have an associated Animation
+ // so we don't need to null-check the result of GetAnimation().
+ return aEffect.IsCurrent() && aEffect.GetAnimation()->AsCSSTransition();
+ }
+ );
+}
+
+bool
+nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame,
+ nsCSSPropertyID aProperty)
+{
+ return HasMatchingAnimations(aFrame,
+ [&aProperty](KeyframeEffectReadOnly& aEffect)
+ {
+ return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
+ aEffect.HasAnimationOfProperty(aProperty);
+ }
+ );
+}
+
+bool
+nsLayoutUtils::HasEffectiveAnimation(const nsIFrame* aFrame,
+ nsCSSPropertyID aProperty)
+{
+ return HasMatchingAnimations(aFrame,
+ [&aProperty](KeyframeEffectReadOnly& aEffect)
+ {
+ return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
+ aEffect.HasEffectiveAnimationOfProperty(aProperty);
+ }
+ );
+}
+
+static float
+GetSuitableScale(float aMaxScale, float aMinScale,
+ nscoord aVisibleDimension, nscoord aDisplayDimension)
+{
+ float displayVisibleRatio = float(aDisplayDimension) /
+ float(aVisibleDimension);
+ // We want to rasterize based on the largest scale used during the
+ // transform animation, unless that would make us rasterize something
+ // larger than the screen. But we never want to go smaller than the
+ // minimum scale over the animation.
+ if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
+ // Using aMaxScale may make us rasterize something a fraction larger than
+ // the screen. However, if aMaxScale happens to be the final scale of a
+ // transform animation it is better to use aMaxScale so that for the
+ // fraction of a second before we delayerize the composited texture it has
+ // a better chance of being pixel aligned and composited without resampling
+ // (avoiding visually clunky delayerization).
+ return aMaxScale;
+ }
+ return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
+}
+
+static void
+GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame,
+ nsTArray<RefPtr<dom::Animation>>&
+ aAnimations,
+ gfxSize& aMaxScale,
+ gfxSize& aMinScale)
+{
+ for (dom::Animation* anim : aAnimations) {
+ // This method is only expected to be passed animations that are running on
+ // the compositor and we only pass playing animations to the compositor,
+ // which are, by definition, "relevant" animations (animations that are
+ // not yet finished or which are filling forwards).
+ MOZ_ASSERT(anim->IsRelevant());
+
+ dom::KeyframeEffectReadOnly* effect =
+ anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
+ MOZ_ASSERT(effect, "A playing animation should have a keyframe effect");
+ for (size_t propIdx = effect->Properties().Length(); propIdx-- != 0; ) {
+ const AnimationProperty& prop = effect->Properties()[propIdx];
+ if (prop.mProperty == eCSSProperty_transform) {
+ for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) {
+ const AnimationPropertySegment& segment = prop.mSegments[segIdx];
+ gfxSize from = segment.mFromValue.GetScaleValue(aFrame);
+ aMaxScale.width = std::max<float>(aMaxScale.width, from.width);
+ aMaxScale.height = std::max<float>(aMaxScale.height, from.height);
+ aMinScale.width = std::min<float>(aMinScale.width, from.width);
+ aMinScale.height = std::min<float>(aMinScale.height, from.height);
+ gfxSize to = segment.mToValue.GetScaleValue(aFrame);
+ aMaxScale.width = std::max<float>(aMaxScale.width, to.width);
+ aMaxScale.height = std::max<float>(aMaxScale.height, to.height);
+ aMinScale.width = std::min<float>(aMinScale.width, to.width);
+ aMinScale.height = std::min<float>(aMinScale.height, to.height);
+ }
+ }
+ }
+ }
+}
+
+gfxSize
+nsLayoutUtils::ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
+ const nsSize& aVisibleSize,
+ const nsSize& aDisplaySize)
+{
+ gfxSize maxScale(std::numeric_limits<gfxFloat>::min(),
+ std::numeric_limits<gfxFloat>::min());
+ gfxSize minScale(std::numeric_limits<gfxFloat>::max(),
+ std::numeric_limits<gfxFloat>::max());
+
+ nsTArray<RefPtr<dom::Animation>> compositorAnimations =
+ EffectCompositor::GetAnimationsForCompositor(aFrame,
+ eCSSProperty_transform);
+ GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations,
+ maxScale, minScale);
+
+ if (maxScale.width == std::numeric_limits<gfxFloat>::min()) {
+ // We didn't encounter a transform
+ return gfxSize(1.0, 1.0);
+ }
+
+ return gfxSize(GetSuitableScale(maxScale.width, minScale.width,
+ aVisibleSize.width, aDisplaySize.width),
+ GetSuitableScale(maxScale.height, minScale.height,
+ aVisibleSize.height, aDisplaySize.height));
+}
+
+bool
+nsLayoutUtils::AreAsyncAnimationsEnabled()
+{
+ static bool sAreAsyncAnimationsEnabled;
+ static bool sAsyncPrefCached = false;
+
+ if (!sAsyncPrefCached) {
+ sAsyncPrefCached = true;
+ Preferences::AddBoolVarCache(&sAreAsyncAnimationsEnabled,
+ "layers.offmainthreadcomposition.async-animations");
+ }
+
+ return sAreAsyncAnimationsEnabled &&
+ gfxPlatform::OffMainThreadCompositingEnabled();
+}
+
+bool
+nsLayoutUtils::IsAnimationLoggingEnabled()
+{
+ static bool sShouldLog;
+ static bool sShouldLogPrefCached;
+
+ if (!sShouldLogPrefCached) {
+ sShouldLogPrefCached = true;
+ Preferences::AddBoolVarCache(&sShouldLog,
+ "layers.offmainthreadcomposition.log-animations");
+ }
+
+ return sShouldLog;
+}
+
+bool
+nsLayoutUtils::GPUImageScalingEnabled()
+{
+ static bool sGPUImageScalingEnabled;
+ static bool sGPUImageScalingPrefInitialised = false;
+
+ if (!sGPUImageScalingPrefInitialised) {
+ sGPUImageScalingPrefInitialised = true;
+ sGPUImageScalingEnabled =
+ Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
+ }
+
+ return sGPUImageScalingEnabled;
+}
+
+bool
+nsLayoutUtils::AnimatedImageLayersEnabled()
+{
+ static bool sAnimatedImageLayersEnabled;
+ static bool sAnimatedImageLayersPrefCached = false;
+
+ if (!sAnimatedImageLayersPrefCached) {
+ sAnimatedImageLayersPrefCached = true;
+ Preferences::AddBoolVarCache(&sAnimatedImageLayersEnabled,
+ "layout.animated-image-layers.enabled",
+ false);
+ }
+
+ return sAnimatedImageLayersEnabled;
+}
+
+bool
+nsLayoutUtils::CSSFiltersEnabled()
+{
+ static bool sCSSFiltersEnabled;
+ static bool sCSSFiltersPrefCached = false;
+
+ if (!sCSSFiltersPrefCached) {
+ sCSSFiltersPrefCached = true;
+ Preferences::AddBoolVarCache(&sCSSFiltersEnabled,
+ "layout.css.filters.enabled",
+ false);
+ }
+
+ return sCSSFiltersEnabled;
+}
+
+bool
+nsLayoutUtils::CSSClipPathShapesEnabled()
+{
+ static bool sCSSClipPathShapesEnabled;
+ static bool sCSSClipPathShapesPrefCached = false;
+
+ if (!sCSSClipPathShapesPrefCached) {
+ sCSSClipPathShapesPrefCached = true;
+ Preferences::AddBoolVarCache(&sCSSClipPathShapesEnabled,
+ "layout.css.clip-path-shapes.enabled",
+ false);
+ }
+
+ return sCSSClipPathShapesEnabled;
+}
+
+bool
+nsLayoutUtils::UnsetValueEnabled()
+{
+ static bool sUnsetValueEnabled;
+ static bool sUnsetValuePrefCached = false;
+
+ if (!sUnsetValuePrefCached) {
+ sUnsetValuePrefCached = true;
+ Preferences::AddBoolVarCache(&sUnsetValueEnabled,
+ "layout.css.unset-value.enabled",
+ false);
+ }
+
+ return sUnsetValueEnabled;
+}
+
+bool
+nsLayoutUtils::IsGridTemplateSubgridValueEnabled()
+{
+ static bool sGridTemplateSubgridValueEnabled;
+ static bool sGridTemplateSubgridValueEnabledPrefCached = false;
+
+ if (!sGridTemplateSubgridValueEnabledPrefCached) {
+ sGridTemplateSubgridValueEnabledPrefCached = true;
+ Preferences::AddBoolVarCache(&sGridTemplateSubgridValueEnabled,
+ GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME,
+ false);
+ }
+
+ return sGridTemplateSubgridValueEnabled;
+}
+
+bool
+nsLayoutUtils::IsTextAlignUnsafeValueEnabled()
+{
+ static bool sTextAlignUnsafeValueEnabled;
+ static bool sTextAlignUnsafeValueEnabledPrefCached = false;
+
+ if (!sTextAlignUnsafeValueEnabledPrefCached) {
+ sTextAlignUnsafeValueEnabledPrefCached = true;
+ Preferences::AddBoolVarCache(&sTextAlignUnsafeValueEnabled,
+ TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME,
+ false);
+ }
+
+ return sTextAlignUnsafeValueEnabled;
+}
+
+void
+nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
+ nsOverflowAreas& aOverflowAreas,
+ FrameChildListIDs aSkipChildLists)
+{
+ // Iterate over all children except pop-ups.
+ FrameChildListIDs skip = aSkipChildLists |
+ nsIFrame::kSelectPopupList | nsIFrame::kPopupList;
+ for (nsIFrame::ChildListIterator childLists(aFrame);
+ !childLists.IsDone(); childLists.Next()) {
+ if (skip.Contains(childLists.CurrentID())) {
+ continue;
+ }
+
+ nsFrameList children = childLists.CurrentList();
+ for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
+ nsIFrame* child = e.get();
+ nsOverflowAreas childOverflow =
+ child->GetOverflowAreas() + child->GetPosition();
+ aOverflowAreas.UnionWith(childOverflow);
+ }
+ }
+}
+
+static void DestroyViewID(void* aObject, nsIAtom* aPropertyName,
+ void* aPropertyValue, void* aData)
+{
+ ViewID* id = static_cast<ViewID*>(aPropertyValue);
+ GetContentMap().Remove(*id);
+ delete id;
+}
+
+/**
+ * A namespace class for static layout utilities.
+ */
+
+bool
+nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId)
+{
+ void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId);
+ if (scrollIdProperty) {
+ *aOutViewId = *static_cast<ViewID*>(scrollIdProperty);
+ return true;
+ }
+ return false;
+}
+
+ViewID
+nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent)
+{
+ ViewID scrollId;
+
+ if (!FindIDFor(aContent, &scrollId)) {
+ scrollId = sScrollIdCounter++;
+ aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
+ DestroyViewID);
+ GetContentMap().Put(scrollId, aContent);
+ }
+
+ return scrollId;
+}
+
+nsIContent*
+nsLayoutUtils::FindContentFor(ViewID aId)
+{
+ MOZ_ASSERT(aId != FrameMetrics::NULL_SCROLL_ID,
+ "Cannot find a content element in map for null IDs.");
+ nsIContent* content;
+ bool exists = GetContentMap().Get(aId, &content);
+
+ if (exists) {
+ return content;
+ } else {
+ return nullptr;
+ }
+}
+
+nsIFrame*
+GetScrollFrameFromContent(nsIContent* aContent)
+{
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (aContent->OwnerDoc()->GetRootElement() == aContent) {
+ nsIPresShell* presShell = frame ? frame->PresContext()->PresShell() : nullptr;
+ if (!presShell) {
+ presShell = aContent->OwnerDoc()->GetShell();
+ }
+ // We want the scroll frame, the root scroll frame differs from all
+ // others in that the primary frame is not the scroll frame.
+ nsIFrame* rootScrollFrame = presShell ? presShell->GetRootScrollFrame() : nullptr;
+ if (rootScrollFrame) {
+ frame = rootScrollFrame;
+ }
+ }
+ return frame;
+}
+
+nsIScrollableFrame*
+nsLayoutUtils::FindScrollableFrameFor(ViewID aId)
+{
+ nsIContent* content = FindContentFor(aId);
+ if (!content) {
+ return nullptr;
+ }
+
+ nsIFrame* scrollFrame = GetScrollFrameFromContent(content);
+ return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
+}
+
+static nsRect
+ApplyRectMultiplier(nsRect aRect, float aMultiplier)
+{
+ if (aMultiplier == 1.0f) {
+ return aRect;
+ }
+ float newWidth = aRect.width * aMultiplier;
+ float newHeight = aRect.height * aMultiplier;
+ float newX = aRect.x - ((newWidth - aRect.width) / 2.0f);
+ float newY = aRect.y - ((newHeight - aRect.height) / 2.0f);
+ // Rounding doesn't matter too much here, do a round-in
+ return nsRect(ceil(newX), ceil(newY), floor(newWidth), floor(newHeight));
+}
+
+bool
+nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame)
+{
+#ifdef MOZ_WIDGET_ANDROID
+ // We always have async scrolling for android
+ return true;
+#endif
+
+ return AsyncPanZoomEnabled(aFrame);
+}
+
+bool
+nsLayoutUtils::AsyncPanZoomEnabled(nsIFrame* aFrame)
+{
+ // We use this as a shortcut, since if the compositor will never use APZ,
+ // no widget will either.
+ if (!gfxPlatform::AsyncPanZoomEnabled()) {
+ return false;
+ }
+
+ nsIFrame *frame = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ nsIWidget* widget = frame->GetNearestWidget();
+ if (!widget) {
+ return false;
+ }
+ return widget->AsyncPanZoomEnabled();
+}
+
+float
+nsLayoutUtils::GetCurrentAPZResolutionScale(nsIPresShell* aShell) {
+ return aShell ? aShell->GetCumulativeNonRootScaleResolution() : 1.0;
+}
+
+// Return the maximum displayport size, based on the LayerManager's maximum
+// supported texture size. The result is in app units.
+static nscoord
+GetMaxDisplayPortSize(nsIContent* aContent)
+{
+ MOZ_ASSERT(!gfxPrefs::LayersTilesEnabled(), "Do not clamp displayports if tiling is enabled");
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return nscoord_MAX;
+ }
+ frame = nsLayoutUtils::GetDisplayRootFrame(frame);
+
+ nsIWidget* widget = frame->GetNearestWidget();
+ if (!widget) {
+ return nscoord_MAX;
+ }
+ LayerManager* lm = widget->GetLayerManager();
+ if (!lm) {
+ return nscoord_MAX;
+ }
+ nsPresContext* presContext = frame->PresContext();
+
+ int32_t maxSizeInDevPixels = lm->GetMaxTextureSize();
+ if (maxSizeInDevPixels < 0 || maxSizeInDevPixels == INT_MAX) {
+ return nscoord_MAX;
+ }
+ return presContext->DevPixelsToAppUnits(maxSizeInDevPixels);
+}
+
+static nsRect
+GetDisplayPortFromRectData(nsIContent* aContent,
+ DisplayPortPropertyData* aRectData,
+ float aMultiplier)
+{
+ // 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). We still do need to
+ // expand it by the multiplier though.
+ return ApplyRectMultiplier(aRectData->mRect, aMultiplier);
+}
+
+static nsRect
+GetDisplayPortFromMarginsData(nsIContent* aContent,
+ DisplayPortMarginsPropertyData* aMarginsData,
+ float aMultiplier)
+{
+ // 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, further expand the rect by the multiplier, and finally, clamp it
+ // to the size of the scrollable rect.
+
+ nsRect base;
+ if (nsRect* baseData = static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
+ base = *baseData;
+ } else {
+ // In theory we shouldn't get here, but we do sometimes (see bug 1212136).
+ // Fall through for graceful handling.
+ }
+
+ nsIFrame* frame = GetScrollFrameFromContent(aContent);
+ if (!frame) {
+ // Turns out we can't really compute it. Oops. We still should return
+ // something sane. Note that since we can't clamp the rect without a
+ // frame, we don't apply the multiplier either as it can cause the result
+ // to leak outside the scrollable area.
+ 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;
+ }
+
+ nsPoint scrollPos;
+ if (nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame()) {
+ scrollPos = scrollableFrame->GetScrollPosition();
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ LayoutDeviceToScreenScale2D res(presContext->PresShell()->GetCumulativeResolution()
+ * nsLayoutUtils::GetTransformToAncestorScale(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;
+
+ if (APZCCallbackHelper::IsDisplayportSuppressed()) {
+ alignment = ScreenSize(1, 1);
+ } else if (gfxPrefs::LayersTilesEnabled()) {
+ // Don't align to tiles if they are too large, because we could expand
+ // the displayport by a lot which can take more paint time. It's a tradeoff
+ // though because if we don't align to tiles we have more waste on upload.
+ IntSize tileSize = gfxVars::TileSize();
+ alignment = ScreenSize(std::min(256, tileSize.width), std::min(256, tileSize.height));
+ } else {
+ // If we're not drawing with tiles then we need to be careful about not
+ // hitting the max texture size and we only need 1 draw call per layer
+ // so we can align to a smaller multiple.
+ alignment = ScreenSize(128, 128);
+ }
+
+ // Avoid division by zero.
+ if (alignment.width == 0) {
+ alignment.width = 128;
+ }
+ if (alignment.height == 0) {
+ alignment.height = 128;
+ }
+
+ if (gfxPrefs::LayersTilesEnabled()) {
+ // Expand the rect by the margins
+ screenRect.Inflate(aMarginsData->mMargins);
+ } else {
+ // Calculate the displayport to make sure we fit within the max texture size
+ // when not tiling.
+ nscoord maxSizeAppUnits = GetMaxDisplayPortSize(aContent);
+ if (maxSizeAppUnits == nscoord_MAX) {
+ // Pick a safe maximum displayport size for sanity purposes. This is the
+ // lowest maximum texture size on tileless-platforms (Windows, D3D10).
+ maxSizeAppUnits = presContext->DevPixelsToAppUnits(8192);
+ }
+
+ // The alignment code can round up to 3 tiles, we want to make sure
+ // that the displayport can grow by up to 3 tiles without going
+ // over the max texture size.
+ const int MAX_ALIGN_ROUNDING = 3;
+
+ // Find the maximum size in screen pixels.
+ int32_t maxSizeDevPx = presContext->AppUnitsToDevPixels(maxSizeAppUnits);
+ int32_t maxWidthScreenPx = floor(double(maxSizeDevPx) * res.xScale) -
+ MAX_ALIGN_ROUNDING * alignment.width;
+ int32_t maxHeightScreenPx = floor(double(maxSizeDevPx) * res.yScale) -
+ MAX_ALIGN_ROUNDING * alignment.height;
+
+ // For each axis, inflate the margins up to the maximum size.
+ const ScreenMargin& margins = aMarginsData->mMargins;
+ if (screenRect.height < maxHeightScreenPx) {
+ int32_t budget = maxHeightScreenPx - screenRect.height;
+ // Scale the margins down to fit into the budget if necessary, maintaining
+ // their relative ratio.
+ float scale = std::min(1.0f, float(budget) / margins.TopBottom());
+ float top = margins.top * scale;
+ float bottom = margins.bottom * scale;
+ screenRect.y -= top;
+ screenRect.height += top + bottom;
+ }
+ if (screenRect.width < maxWidthScreenPx) {
+ int32_t budget = maxWidthScreenPx - screenRect.width;
+ float scale = std::min(1.0f, float(budget) / margins.LeftRight());
+ float left = margins.left * scale;
+ float right = margins.right * scale;
+ screenRect.x -= left;
+ screenRect.width += left + right;
+ }
+ }
+
+ ScreenPoint scrollPosScreen = LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel)
+ * res;
+
+ // Round-out the display port to the nearest alignment (tiles)
+ 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);
+
+ // If we have non-zero margins, expand the displayport for the low-res buffer
+ // if that's what we're drawing. If we have zero margins, we want the
+ // displayport to reflect the scrollport.
+ if (aMarginsData->mMargins != ScreenMargin()) {
+ result = ApplyRectMultiplier(result, aMultiplier);
+ }
+
+ // Make sure the displayport remains within the scrollable rect.
+ result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
+
+ return result;
+}
+
+static bool
+ShouldDisableApzForElement(nsIContent* aContent)
+{
+ if (gfxPrefs::APZDisableForScrollLinkedEffects() && aContent) {
+ nsIDocument* doc = aContent->GetComposedDoc();
+ return (doc && doc->HasScrollLinkedEffect());
+ }
+ return false;
+}
+
+static bool
+GetDisplayPortImpl(nsIContent* aContent, nsRect *aResult, float aMultiplier)
+{
+ DisplayPortPropertyData* rectData =
+ static_cast<DisplayPortPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPort));
+ DisplayPortMarginsPropertyData* marginsData =
+ static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+
+ if (!rectData && !marginsData) {
+ // This content element has no displayport data at all
+ 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;
+ }
+
+ if (rectData && marginsData) {
+ // choose margins if equal priority
+ if (rectData->mPriority > marginsData->mPriority) {
+ marginsData = nullptr;
+ } else {
+ rectData = nullptr;
+ }
+ }
+
+ NS_ASSERTION((rectData == nullptr) != (marginsData == nullptr),
+ "Only one of rectData or marginsData should be set!");
+
+ nsRect result;
+ if (rectData) {
+ result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier);
+ } else if (APZCCallbackHelper::IsDisplayportSuppressed() || ShouldDisableApzForElement(aContent)) {
+ DisplayPortMarginsPropertyData noMargins(ScreenMargin(), 1);
+ result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier);
+ } else {
+ result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier);
+ }
+
+ if (!gfxPrefs::LayersTilesEnabled()) {
+ // Either we should have gotten a valid rect directly from the displayport
+ // base, or we should have computed a valid rect from the margins.
+ NS_ASSERTION(result.width <= GetMaxDisplayPortSize(aContent),
+ "Displayport must be a valid texture size");
+ NS_ASSERTION(result.height <= GetMaxDisplayPortSize(aContent),
+ "Displayport must be a valid texture size");
+ }
+
+ *aResult = result;
+ return true;
+}
+
+void
+TranslateFromScrollPortToScrollFrame(nsIContent* aContent, nsRect* aRect)
+{
+ MOZ_ASSERT(aRect);
+ nsIFrame* frame = GetScrollFrameFromContent(aContent);
+ nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
+ if (scrollableFrame) {
+ *aRect += scrollableFrame->GetScrollPortRect().TopLeft();
+ }
+}
+
+bool
+nsLayoutUtils::GetDisplayPort(nsIContent* aContent, nsRect *aResult,
+ RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */)
+{
+ float multiplier =
+ gfxPrefs::UseLowPrecisionBuffer() ? 1.0f / gfxPrefs::LowPrecisionResolution() : 1.0f;
+ bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, multiplier);
+ if (aResult && usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) {
+ TranslateFromScrollPortToScrollFrame(aContent, aResult);
+ }
+ return usingDisplayPort;
+}
+
+bool
+nsLayoutUtils::HasDisplayPort(nsIContent* aContent) {
+ return GetDisplayPort(aContent, nullptr);
+}
+
+/* static */ bool
+nsLayoutUtils::GetDisplayPortForVisibilityTesting(
+ nsIContent* aContent,
+ nsRect* aResult,
+ RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */)
+{
+ MOZ_ASSERT(aResult);
+ bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, 1.0f);
+ if (usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) {
+ TranslateFromScrollPortToScrollFrame(aContent, aResult);
+ }
+ return usingDisplayPort;
+}
+
+bool
+nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent,
+ nsIPresShell* aPresShell,
+ const ScreenMargin& aMargins,
+ uint32_t aPriority,
+ RepaintMode aRepaintMode)
+{
+ MOZ_ASSERT(aContent);
+ MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument());
+
+ DisplayPortMarginsPropertyData* currentData =
+ static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+ if (currentData && currentData->mPriority > aPriority) {
+ return false;
+ }
+
+ nsRect oldDisplayPort;
+ bool hadDisplayPort = GetHighResolutionDisplayPort(aContent, &oldDisplayPort);
+
+ aContent->SetProperty(nsGkAtoms::DisplayPortMargins,
+ new DisplayPortMarginsPropertyData(
+ aMargins, aPriority),
+ nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
+
+ nsRect newDisplayPort;
+ DebugOnly<bool> hasDisplayPort = GetHighResolutionDisplayPort(aContent, &newDisplayPort);
+ MOZ_ASSERT(hasDisplayPort);
+
+ bool changed = !hadDisplayPort ||
+ !oldDisplayPort.IsEqualEdges(newDisplayPort);
+
+ if (gfxPrefs::LayoutUseContainersForRootFrames()) {
+ nsIFrame* rootScrollFrame = aPresShell->GetRootScrollFrame();
+ if (rootScrollFrame &&
+ aContent == rootScrollFrame->GetContent() &&
+ nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame))
+ {
+ // We are setting a root displayport for a document.
+ // If we have APZ, then set a special flag on the pres shell so
+ // that we don't get scrollbars drawn.
+ aPresShell->SetIgnoreViewportScrolling(true);
+ }
+ }
+
+ if (changed && aRepaintMode == RepaintMode::Repaint) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ frame->SchedulePaint();
+ }
+ }
+
+ nsIFrame* frame = GetScrollFrameFromContent(aContent);
+ nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
+ if (!scrollableFrame) {
+ return true;
+ }
+
+ scrollableFrame->TriggerDisplayPortExpiration();
+
+ // Display port margins changing means that the set of visible frames may
+ // have drastically changed. Check if we should schedule an update.
+ hadDisplayPort =
+ scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(&oldDisplayPort);
+
+ bool needVisibilityUpdate = !hadDisplayPort;
+ // Check if the total size has changed by a large factor.
+ if (!needVisibilityUpdate) {
+ if ((newDisplayPort.width > 2 * oldDisplayPort.width) ||
+ (oldDisplayPort.width > 2 * newDisplayPort.width) ||
+ (newDisplayPort.height > 2 * oldDisplayPort.height) ||
+ (oldDisplayPort.height > 2 * newDisplayPort.height)) {
+ needVisibilityUpdate = true;
+ }
+ }
+ // Check if it's moved by a significant amount.
+ if (!needVisibilityUpdate) {
+ if (nsRect* baseData = static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
+ nsRect base = *baseData;
+ if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) ||
+ (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > base.width) ||
+ (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
+ (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > base.height)) {
+ needVisibilityUpdate = true;
+ }
+ }
+ }
+ if (needVisibilityUpdate) {
+ aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+
+ return true;
+}
+
+void
+nsLayoutUtils::SetDisplayPortBase(nsIContent* aContent, const nsRect& aBase)
+{
+ aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
+ nsINode::DeleteProperty<nsRect>);
+}
+
+void
+nsLayoutUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent, const nsRect& aBase)
+{
+ if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) {
+ SetDisplayPortBase(aContent, aBase);
+ }
+}
+
+bool
+nsLayoutUtils::GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult)
+{
+ if (gfxPrefs::UseLowPrecisionBuffer()) {
+ return GetDisplayPortImpl(aContent, aResult, 1.0f);
+ }
+ return false;
+}
+
+bool
+nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent)
+{
+ return GetCriticalDisplayPort(aContent, nullptr);
+}
+
+bool
+nsLayoutUtils::GetHighResolutionDisplayPort(nsIContent* aContent, nsRect* aResult)
+{
+ if (gfxPrefs::UseLowPrecisionBuffer()) {
+ return GetCriticalDisplayPort(aContent, aResult);
+ }
+ return GetDisplayPort(aContent, aResult);
+}
+
+void
+nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent)
+{
+ aContent->DeleteProperty(nsGkAtoms::DisplayPort);
+ aContent->DeleteProperty(nsGkAtoms::DisplayPortMargins);
+}
+
+nsContainerFrame*
+nsLayoutUtils::LastContinuationWithChild(nsContainerFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame, "NULL frame pointer");
+ nsIFrame* f = aFrame->LastContinuation();
+ while (!f->PrincipalChildList().FirstChild() && f->GetPrevContinuation()) {
+ f = f->GetPrevContinuation();
+ }
+ return static_cast<nsContainerFrame*>(f);
+}
+
+//static
+FrameChildListID
+nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame)
+{
+ nsIFrame::ChildListID id = nsIFrame::kPrincipalList;
+
+ if (aChildFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
+ nsIFrame* pif = aChildFrame->GetPrevInFlow();
+ if (pif->GetParent() == aChildFrame->GetParent()) {
+ id = nsIFrame::kExcessOverflowContainersList;
+ }
+ else {
+ id = nsIFrame::kOverflowContainersList;
+ }
+ }
+ // See if the frame is moved out of the flow
+ else if (aChildFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ // Look at the style information to tell
+ const nsStyleDisplay* disp = aChildFrame->StyleDisplay();
+
+ if (NS_STYLE_POSITION_ABSOLUTE == disp->mPosition) {
+ id = nsIFrame::kAbsoluteList;
+ } else if (NS_STYLE_POSITION_FIXED == disp->mPosition) {
+ if (nsLayoutUtils::IsReallyFixedPos(aChildFrame)) {
+ id = nsIFrame::kFixedList;
+ } else {
+ id = nsIFrame::kAbsoluteList;
+ }
+#ifdef MOZ_XUL
+ } else if (StyleDisplay::Popup == disp->mDisplay) {
+ // Out-of-flows that are DISPLAY_POPUP must be kids of the root popup set
+#ifdef DEBUG
+ nsIFrame* parent = aChildFrame->GetParent();
+ NS_ASSERTION(parent && parent->GetType() == nsGkAtoms::popupSetFrame,
+ "Unexpected parent");
+#endif // DEBUG
+
+ id = nsIFrame::kPopupList;
+#endif // MOZ_XUL
+ } else {
+ NS_ASSERTION(aChildFrame->IsFloating(), "not a floated frame");
+ id = nsIFrame::kFloatList;
+ }
+
+ } else {
+ nsIAtom* childType = aChildFrame->GetType();
+ if (nsGkAtoms::menuPopupFrame == childType) {
+ nsIFrame* parent = aChildFrame->GetParent();
+ MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame");
+ if (parent) {
+ if (parent->GetType() == nsGkAtoms::popupSetFrame) {
+ id = nsIFrame::kPopupList;
+ } else {
+ nsIFrame* firstPopup = parent->GetChildList(nsIFrame::kPopupList).FirstChild();
+ MOZ_ASSERT(!firstPopup || !firstPopup->GetNextSibling(),
+ "We assume popupList only has one child, but it has more.");
+ id = firstPopup == aChildFrame
+ ? nsIFrame::kPopupList
+ : nsIFrame::kPrincipalList;
+ }
+ } else {
+ id = nsIFrame::kPrincipalList;
+ }
+ } else if (nsGkAtoms::tableColGroupFrame == childType) {
+ id = nsIFrame::kColGroupList;
+ } else if (aChildFrame->IsTableCaption()) {
+ id = nsIFrame::kCaptionList;
+ } else {
+ id = nsIFrame::kPrincipalList;
+ }
+ }
+
+#ifdef DEBUG
+ // Verify that the frame is actually in that child list or in the
+ // corresponding overflow list.
+ nsContainerFrame* parent = aChildFrame->GetParent();
+ bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
+ if (!found) {
+ if (!(aChildFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ found = parent->GetChildList(nsIFrame::kOverflowList)
+ .ContainsFrame(aChildFrame);
+ }
+ else if (aChildFrame->IsFloating()) {
+ found = parent->GetChildList(nsIFrame::kOverflowOutOfFlowList)
+ .ContainsFrame(aChildFrame);
+ if (!found) {
+ found = parent->GetChildList(nsIFrame::kPushedFloatsList)
+ .ContainsFrame(aChildFrame);
+ }
+ }
+ // else it's positioned and should have been on the 'id' child list.
+ NS_POSTCONDITION(found, "not in child list");
+ }
+#endif
+
+ return id;
+}
+
+/*static*/ nsIFrame*
+nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame,
+ const nsIContent* aContent)
+{
+ // We need to call GetGenConPseudos() on the first continuation/ib-split.
+ // Find it, for symmetry with GetAfterFrameForContent.
+ nsContainerFrame* genConParentFrame =
+ FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
+ if (!genConParentFrame) {
+ return nullptr;
+ }
+ nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
+ if (prop) {
+ const nsTArray<nsIContent*>& pseudos(*prop);
+ for (uint32_t i = 0; i < pseudos.Length(); ++i) {
+ if (pseudos[i]->GetParent() == aContent &&
+ pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) {
+ return pseudos[i]->GetPrimaryFrame();
+ }
+ }
+ }
+ // If the first child frame is a pseudo-frame, then try that.
+ // Note that the frame we create for the generated content is also a
+ // pseudo-frame and so don't drill down in that case.
+ nsIFrame* childFrame = genConParentFrame->PrincipalChildList().FirstChild();
+ if (childFrame &&
+ childFrame->IsPseudoFrame(aContent) &&
+ !childFrame->IsGeneratedContentFrame()) {
+ return GetBeforeFrameForContent(childFrame, aContent);
+ }
+ return nullptr;
+}
+
+/*static*/ nsIFrame*
+nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame)
+{
+ return GetBeforeFrameForContent(aFrame, aFrame->GetContent());
+}
+
+/*static*/ nsIFrame*
+nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame,
+ const nsIContent* aContent)
+{
+ // We need to call GetGenConPseudos() on the first continuation,
+ // but callers are likely to pass the last.
+ nsContainerFrame* genConParentFrame =
+ FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
+ if (!genConParentFrame) {
+ return nullptr;
+ }
+ nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
+ if (prop) {
+ const nsTArray<nsIContent*>& pseudos(*prop);
+ for (uint32_t i = 0; i < pseudos.Length(); ++i) {
+ if (pseudos[i]->GetParent() == aContent &&
+ pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) {
+ return pseudos[i]->GetPrimaryFrame();
+ }
+ }
+ }
+ // If the last child frame is a pseudo-frame, then try that.
+ // Note that the frame we create for the generated content is also a
+ // pseudo-frame and so don't drill down in that case.
+ genConParentFrame = aFrame->GetContentInsertionFrame();
+ if (!genConParentFrame) {
+ return nullptr;
+ }
+ nsIFrame* lastParentContinuation =
+ LastContinuationWithChild(static_cast<nsContainerFrame*>(
+ LastContinuationOrIBSplitSibling(genConParentFrame)));
+ nsIFrame* childFrame =
+ lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild();
+ if (childFrame &&
+ childFrame->IsPseudoFrame(aContent) &&
+ !childFrame->IsGeneratedContentFrame()) {
+ return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent);
+ }
+ return nullptr;
+}
+
+/*static*/ nsIFrame*
+nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
+{
+ return GetAfterFrameForContent(aFrame, aFrame->GetContent());
+}
+
+// static
+nsIFrame*
+nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
+ nsIAtom* aFrameType,
+ nsIFrame* aStopAt)
+{
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ if (frame->GetType() == aFrameType) {
+ return frame;
+ }
+ if (frame == aStopAt) {
+ break;
+ }
+ }
+ return nullptr;
+}
+
+// static
+nsIFrame*
+nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame)
+{
+ if (aFrame->GetType() == nsGkAtoms::tableWrapperFrame) {
+ nsIFrame* inner = aFrame->PrincipalChildList().FirstChild();
+ // inner may be null, if aFrame is mid-destruction
+ return inner;
+ }
+
+ return aFrame;
+}
+
+nsIFrame*
+nsLayoutUtils::GetStyleFrame(const nsIContent* aContent)
+{
+ nsIFrame *frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ return nsLayoutUtils::GetStyleFrame(frame);
+}
+
+/* static */ nsIFrame*
+nsLayoutUtils::GetRealPrimaryFrameFor(const nsIContent* aContent)
+{
+ nsIFrame *frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ return nsPlaceholderFrame::GetRealFrameFor(frame);
+}
+
+nsIFrame*
+nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
+ NS_ASSERTION(nsGkAtoms::placeholderFrame == aFrame->GetType(),
+ "Must have a placeholder here");
+ if (aFrame->GetStateBits() & PLACEHOLDER_FOR_FLOAT) {
+ nsIFrame *outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ NS_ASSERTION(outOfFlowFrame->IsFloating(),
+ "How did that happen?");
+ return outOfFlowFrame;
+ }
+
+ return nullptr;
+}
+
+// static
+bool
+nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsIAtom* aPseudoElement)
+{
+ NS_PRECONDITION(aFrame, "Must have a frame");
+ NS_PRECONDITION(aPseudoElement, "Must have a pseudo name");
+
+ if (!aFrame->IsGeneratedContentFrame()) {
+ return false;
+ }
+ nsIFrame* parent = aFrame->GetParent();
+ NS_ASSERTION(parent, "Generated content can't be root frame");
+ if (parent->IsGeneratedContentFrame()) {
+ // Not the root of the generated content
+ return false;
+ }
+
+ if (aContent && parent->GetContent() != aContent) {
+ return false;
+ }
+
+ return (aFrame->GetContent()->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) ==
+ (aPseudoElement == nsCSSPseudoElements::before);
+}
+
+// static
+nsIFrame*
+nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
+ nsPoint* aExtraOffset)
+{
+ nsIFrame* p = aFrame->GetParent();
+ if (p)
+ return p;
+
+ nsView* v = aFrame->GetView();
+ if (!v)
+ return nullptr;
+ v = v->GetParent(); // anonymous inner view
+ if (!v)
+ return nullptr;
+ if (aExtraOffset) {
+ *aExtraOffset += v->GetPosition();
+ }
+ v = v->GetParent(); // subdocumentframe's view
+ return v ? v->GetFrame() : nullptr;
+}
+
+// static
+bool
+nsLayoutUtils::IsProperAncestorFrameCrossDoc(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
+ nsIFrame* aCommonAncestor)
+{
+ if (aFrame == aAncestorFrame)
+ return false;
+ return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor);
+}
+
+// static
+bool
+nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor)
+{
+ for (const nsIFrame* f = aFrame; f != aCommonAncestor;
+ f = GetCrossDocParentFrame(f)) {
+ if (f == aAncestorFrame)
+ return true;
+ }
+ return aCommonAncestor == aAncestorFrame;
+}
+
+// static
+bool
+nsLayoutUtils::IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
+ nsIFrame* aCommonAncestor)
+{
+ if (aFrame == aAncestorFrame)
+ return false;
+ for (nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
+ if (f == aAncestorFrame)
+ return true;
+ }
+ return aCommonAncestor == aAncestorFrame;
+}
+
+// static
+int32_t
+nsLayoutUtils::DoCompareTreePosition(nsIContent* aContent1,
+ nsIContent* aContent2,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ const nsIContent* aCommonAncestor)
+{
+ NS_PRECONDITION(aContent1, "aContent1 must not be null");
+ NS_PRECONDITION(aContent2, "aContent2 must not be null");
+
+ AutoTArray<nsINode*, 32> content1Ancestors;
+ nsINode* c1;
+ for (c1 = aContent1; c1 && c1 != aCommonAncestor; c1 = c1->GetParentNode()) {
+ content1Ancestors.AppendElement(c1);
+ }
+ if (!c1 && aCommonAncestor) {
+ // So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
+ // Never mind. We can continue as if aCommonAncestor was null.
+ aCommonAncestor = nullptr;
+ }
+
+ AutoTArray<nsINode*, 32> content2Ancestors;
+ nsINode* c2;
+ for (c2 = aContent2; c2 && c2 != aCommonAncestor; c2 = c2->GetParentNode()) {
+ content2Ancestors.AppendElement(c2);
+ }
+ if (!c2 && aCommonAncestor) {
+ // So, it turns out aCommonAncestor was not an ancestor of c2.
+ // We need to retry with no common ancestor hint.
+ return DoCompareTreePosition(aContent1, aContent2,
+ aIf1Ancestor, aIf2Ancestor, nullptr);
+ }
+
+ int last1 = content1Ancestors.Length() - 1;
+ int last2 = content2Ancestors.Length() - 1;
+ nsINode* content1Ancestor = nullptr;
+ nsINode* content2Ancestor = nullptr;
+ while (last1 >= 0 && last2 >= 0
+ && ((content1Ancestor = content1Ancestors.ElementAt(last1)) ==
+ (content2Ancestor = content2Ancestors.ElementAt(last2)))) {
+ last1--;
+ last2--;
+ }
+
+ if (last1 < 0) {
+ if (last2 < 0) {
+ NS_ASSERTION(aContent1 == aContent2, "internal error?");
+ return 0;
+ }
+ // aContent1 is an ancestor of aContent2
+ return aIf1Ancestor;
+ }
+
+ if (last2 < 0) {
+ // aContent2 is an ancestor of aContent1
+ return aIf2Ancestor;
+ }
+
+ // content1Ancestor != content2Ancestor, so they must be siblings with the same parent
+ nsINode* parent = content1Ancestor->GetParentNode();
+#ifdef DEBUG
+ // TODO: remove the uglyness, see bug 598468.
+ NS_ASSERTION(gPreventAssertInCompareTreePosition || parent,
+ "no common ancestor at all???");
+#endif // DEBUG
+ if (!parent) { // different documents??
+ return 0;
+ }
+
+ int32_t index1 = parent->IndexOf(content1Ancestor);
+ int32_t index2 = parent->IndexOf(content2Ancestor);
+ if (index1 < 0 || index2 < 0) {
+ // one of them must be anonymous; we can't determine the order
+ return 0;
+ }
+
+ return index1 - index2;
+}
+
+// static
+nsIFrame*
+nsLayoutUtils::FillAncestors(nsIFrame* aFrame,
+ nsIFrame* aStopAtAncestor,
+ nsTArray<nsIFrame*>* aAncestors)
+{
+ while (aFrame && aFrame != aStopAtAncestor) {
+ aAncestors->AppendElement(aFrame);
+ aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
+ }
+ return aFrame;
+}
+
+// Return true if aFrame1 is after aFrame2
+static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2)
+{
+ nsIFrame* f = aFrame2;
+ do {
+ f = f->GetNextSibling();
+ if (f == aFrame1)
+ return true;
+ } while (f);
+ return false;
+}
+
+// static
+int32_t
+nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ nsIFrame* aCommonAncestor)
+{
+ NS_PRECONDITION(aFrame1, "aFrame1 must not be null");
+ NS_PRECONDITION(aFrame2, "aFrame2 must not be null");
+
+ AutoTArray<nsIFrame*,20> frame2Ancestors;
+ nsIFrame* nonCommonAncestor =
+ FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors);
+
+ return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors,
+ aIf1Ancestor, aIf2Ancestor,
+ nonCommonAncestor ? aCommonAncestor : nullptr);
+}
+
+// static
+int32_t
+nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ nsTArray<nsIFrame*>& aFrame2Ancestors,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ nsIFrame* aCommonAncestor)
+{
+ NS_PRECONDITION(aFrame1, "aFrame1 must not be null");
+ NS_PRECONDITION(aFrame2, "aFrame2 must not be null");
+
+ nsPresContext* presContext = aFrame1->PresContext();
+ if (presContext != aFrame2->PresContext()) {
+ NS_ERROR("no common ancestor at all, different documents");
+ return 0;
+ }
+
+ AutoTArray<nsIFrame*,20> frame1Ancestors;
+ if (aCommonAncestor &&
+ !FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) {
+ // We reached the root of the frame tree ... if aCommonAncestor was set,
+ // it is wrong
+ return DoCompareTreePosition(aFrame1, aFrame2,
+ aIf1Ancestor, aIf2Ancestor, nullptr);
+ }
+
+ int32_t last1 = int32_t(frame1Ancestors.Length()) - 1;
+ int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1;
+ while (last1 >= 0 && last2 >= 0 &&
+ frame1Ancestors[last1] == aFrame2Ancestors[last2]) {
+ last1--;
+ last2--;
+ }
+
+ if (last1 < 0) {
+ if (last2 < 0) {
+ NS_ASSERTION(aFrame1 == aFrame2, "internal error?");
+ return 0;
+ }
+ // aFrame1 is an ancestor of aFrame2
+ return aIf1Ancestor;
+ }
+
+ if (last2 < 0) {
+ // aFrame2 is an ancestor of aFrame1
+ return aIf2Ancestor;
+ }
+
+ nsIFrame* ancestor1 = frame1Ancestors[last1];
+ nsIFrame* ancestor2 = aFrame2Ancestors[last2];
+ // Now we should be able to walk sibling chains to find which one is first
+ if (IsFrameAfter(ancestor2, ancestor1))
+ return -1;
+ if (IsFrameAfter(ancestor1, ancestor2))
+ return 1;
+ NS_WARNING("Frames were in different child lists???");
+ return 0;
+}
+
+// static
+nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return nullptr;
+ }
+
+ nsIFrame* next;
+ while ((next = aFrame->GetNextSibling()) != nullptr) {
+ aFrame = next;
+ }
+ return aFrame;
+}
+
+// static
+nsView*
+nsLayoutUtils::FindSiblingViewFor(nsView* aParentView, nsIFrame* aFrame) {
+ nsIFrame* parentViewFrame = aParentView->GetFrame();
+ nsIContent* parentViewContent = parentViewFrame ? parentViewFrame->GetContent() : nullptr;
+ for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore;
+ insertBefore = insertBefore->GetNextSibling()) {
+ nsIFrame* f = insertBefore->GetFrame();
+ if (!f) {
+ // this view could be some anonymous view attached to a meaningful parent
+ for (nsView* searchView = insertBefore->GetParent(); searchView;
+ searchView = searchView->GetParent()) {
+ f = searchView->GetFrame();
+ if (f) {
+ break;
+ }
+ }
+ NS_ASSERTION(f, "Can't find a frame anywhere!");
+ }
+ if (!f || !aFrame->GetContent() || !f->GetContent() ||
+ CompareTreePosition(aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) {
+ // aFrame's content is after f's content (or we just don't know),
+ // so put our view before f's view
+ return insertBefore;
+ }
+ }
+ return nullptr;
+}
+
+//static
+nsIScrollableFrame*
+nsLayoutUtils::GetScrollableFrameFor(const nsIFrame *aScrolledFrame)
+{
+ nsIFrame *frame = aScrolledFrame->GetParent();
+ nsIScrollableFrame *sf = do_QueryFrame(frame);
+ return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr;
+}
+
+/* static */ void
+nsLayoutUtils::SetFixedPositionLayerData(Layer* aLayer,
+ const nsIFrame* aViewportFrame,
+ const nsRect& aAnchorRect,
+ const nsIFrame* aFixedPosFrame,
+ nsPresContext* aPresContext,
+ const ContainerLayerParameters& aContainerParameters) {
+ // Find out the rect of the viewport frame relative to the reference frame.
+ // This, in conjunction with the container scale, will correspond to the
+ // coordinate-space of the built layer.
+ float factor = aPresContext->AppUnitsPerDevPixel();
+ Rect anchorRect(NSAppUnitsToFloatPixels(aAnchorRect.x, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(aAnchorRect.y, factor) *
+ aContainerParameters.mYScale,
+ NSAppUnitsToFloatPixels(aAnchorRect.width, factor) *
+ aContainerParameters.mXScale,
+ NSAppUnitsToFloatPixels(aAnchorRect.height, factor) *
+ aContainerParameters.mYScale);
+ // Need to transform anchorRect from the container layer's coordinate system
+ // into aLayer's coordinate system.
+ Matrix transform2d;
+ if (aLayer->GetTransform().Is2D(&transform2d)) {
+ transform2d.Invert();
+ anchorRect = transform2d.TransformBounds(anchorRect);
+ } else {
+ NS_ERROR("3D transform found between fixedpos content and its viewport (should never happen)");
+ anchorRect = Rect(0,0,0,0);
+ }
+
+ // Work out the anchor point for this fixed position layer. We assume that
+ // any positioning set (left/top/right/bottom) indicates that the
+ // corresponding side of its container should be the anchor point,
+ // defaulting to top-left.
+ LayerPoint anchor(anchorRect.x, anchorRect.y);
+
+ int32_t sides = eSideBitsNone;
+ if (aFixedPosFrame != aViewportFrame) {
+ const nsStylePosition* position = aFixedPosFrame->StylePosition();
+ if (position->mOffset.GetRightUnit() != eStyleUnit_Auto) {
+ sides |= eSideBitsRight;
+ if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) {
+ sides |= eSideBitsLeft;
+ anchor.x = anchorRect.x + anchorRect.width / 2.f;
+ } else {
+ anchor.x = anchorRect.XMost();
+ }
+ }
+ if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto) {
+ sides |= eSideBitsBottom;
+ if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) {
+ sides |= eSideBitsTop;
+ anchor.y = anchorRect.y + anchorRect.height / 2.f;
+ } else {
+ anchor.y = anchorRect.YMost();
+ }
+ }
+ }
+
+ ViewID id = FrameMetrics::NULL_SCROLL_ID;
+ if (nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame()) {
+ if (nsIContent* content = rootScrollFrame->GetContent()) {
+ id = FindOrCreateIDFor(content);
+ }
+ }
+ aLayer->SetFixedPositionData(id, anchor, sides);
+}
+
+bool
+nsLayoutUtils::ViewportHasDisplayPort(nsPresContext* aPresContext)
+{
+ nsIFrame* rootScrollFrame =
+ aPresContext->PresShell()->GetRootScrollFrame();
+ return rootScrollFrame &&
+ nsLayoutUtils::HasDisplayPort(rootScrollFrame->GetContent());
+}
+
+bool
+nsLayoutUtils::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 != NS_STYLE_POSITION_FIXED) {
+ return false;
+ }
+ return ViewportHasDisplayPort(aFrame->PresContext());
+}
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ScrollbarThumbLayerized, bool)
+
+/* static */ void
+nsLayoutUtils::SetScrollbarThumbLayerization(nsIFrame* aThumbFrame, bool aLayerize)
+{
+ aThumbFrame->Properties().Set(ScrollbarThumbLayerized(), aLayerize);
+}
+
+bool
+nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame)
+{
+ return aThumbFrame->Properties().Get(ScrollbarThumbLayerized());
+}
+
+// static
+nsIScrollableFrame*
+nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame,
+ Direction aDirection)
+{
+ NS_ASSERTION(aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
+ for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
+ if (scrollableFrame) {
+ ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles();
+ uint32_t directions = scrollableFrame->GetPerceivedScrollingDirections();
+ if (aDirection == eVertical ?
+ (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN &&
+ (directions & nsIScrollableFrame::VERTICAL)) :
+ (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
+ (directions & nsIScrollableFrame::HORIZONTAL)))
+ return scrollableFrame;
+ }
+ }
+ return nullptr;
+}
+
+// static
+nsIScrollableFrame*
+nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame, uint32_t aFlags)
+{
+ NS_ASSERTION(aFrame, "GetNearestScrollableFrame expects a non-null frame");
+ for (nsIFrame* f = aFrame; f; f = (aFlags & SCROLLABLE_SAME_DOC) ?
+ f->GetParent() : nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
+ if (scrollableFrame) {
+ if (aFlags & SCROLLABLE_ONLY_ASYNC_SCROLLABLE) {
+ if (scrollableFrame->WantAsyncScroll()) {
+ return scrollableFrame;
+ }
+ } else {
+ ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles();
+ if ((aFlags & SCROLLABLE_INCLUDE_HIDDEN) ||
+ ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN ||
+ ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
+ return scrollableFrame;
+ }
+ }
+ if (aFlags & SCROLLABLE_ALWAYS_MATCH_ROOT) {
+ nsIPresShell* ps = f->PresContext()->PresShell();
+ if (ps->GetRootScrollFrame() == f &&
+ ps->GetDocument() && ps->GetDocument()->IsRootDisplayDocument()) {
+ return scrollableFrame;
+ }
+ }
+ }
+ if ((aFlags & SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
+ f->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED &&
+ nsLayoutUtils::IsReallyFixedPos(f)) {
+ return f->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
+ }
+ }
+ return nullptr;
+}
+
+// static
+nsRect
+nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledFrameOverflowArea,
+ const nsSize& aScrollPortSize,
+ uint8_t aDirection)
+{
+ WritingMode wm = aScrolledFrame->GetWritingMode();
+ // Potentially override the frame's direction to use the direction found
+ // by ScrollFrameHelper::GetScrolledFrameDir()
+ wm.SetDirectionFromBidiLevel(aDirection == NS_STYLE_DIRECTION_RTL ? 1 : 0);
+
+ nscoord x1 = aScrolledFrameOverflowArea.x,
+ x2 = aScrolledFrameOverflowArea.XMost(),
+ y1 = aScrolledFrameOverflowArea.y,
+ y2 = aScrolledFrameOverflowArea.YMost();
+
+ bool horizontal = !wm.IsVertical();
+
+ // Clamp the horizontal start-edge (x1 or x2, depending whether the logical
+ // axis that corresponds to horizontal progresses from L-R or R-L).
+ // In horizontal writing mode, we need to check IsInlineReversed() to see
+ // which side to clamp; in vertical mode, it depends on the block direction.
+ if ((horizontal && !wm.IsInlineReversed()) || wm.IsVerticalLR()) {
+ if (x1 < 0) {
+ x1 = 0;
+ }
+ } else {
+ if (x2 > aScrollPortSize.width) {
+ x2 = aScrollPortSize.width;
+ }
+ // When the scrolled frame chooses a size larger than its available width
+ // (because its padding alone is larger than the available width), we need
+ // to keep the start-edge of the scroll frame anchored to the start-edge of
+ // the scrollport.
+ // When the scrolled frame is RTL, this means moving it in our left-based
+ // coordinate system, so we need to compensate for its extra width here by
+ // effectively repositioning the frame.
+ nscoord extraWidth =
+ std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
+ x2 += extraWidth;
+ }
+
+ // Similarly, clamp the vertical start-edge.
+ // In horizontal writing mode, the block direction is always top-to-bottom;
+ // in vertical writing mode, we need to check IsInlineReversed().
+ if (horizontal || !wm.IsInlineReversed()) {
+ if (y1 < 0) {
+ y1 = 0;
+ }
+ } else {
+ if (y2 > aScrollPortSize.height) {
+ y2 = aScrollPortSize.height;
+ }
+ nscoord extraHeight =
+ std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
+ y2 += extraHeight;
+ }
+
+ return nsRect(x1, y1, x2 - x1, y2 - y1);
+}
+
+//static
+bool
+nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ CSSPseudoElementType aPseudoElement,
+ nsPresContext* aPresContext)
+{
+ NS_PRECONDITION(aPresContext, "Must have a prescontext");
+
+ RefPtr<nsStyleContext> pseudoContext;
+ if (aContent) {
+ pseudoContext = aPresContext->StyleSet()->
+ ProbePseudoElementStyle(aContent->AsElement(), aPseudoElement,
+ aStyleContext);
+ }
+ return pseudoContext != nullptr;
+}
+
+nsPoint
+nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(nsIDOMEvent* aDOMEvent, nsIFrame* aFrame)
+{
+ if (!aDOMEvent)
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ WidgetEvent* event = aDOMEvent->WidgetEventPtr();
+ if (!event)
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ return GetEventCoordinatesRelativeTo(event, aFrame);
+}
+
+nsPoint
+nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
+ nsIFrame* aFrame)
+{
+ if (!aEvent || (aEvent->mClass != eMouseEventClass &&
+ aEvent->mClass != eMouseScrollEventClass &&
+ aEvent->mClass != eWheelEventClass &&
+ aEvent->mClass != eDragEventClass &&
+ aEvent->mClass != eSimpleGestureEventClass &&
+ aEvent->mClass != ePointerEventClass &&
+ aEvent->mClass != eGestureNotifyEventClass &&
+ aEvent->mClass != eTouchEventClass &&
+ aEvent->mClass != eQueryContentEventClass))
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+
+ return GetEventCoordinatesRelativeTo(aEvent,
+ aEvent->AsGUIEvent()->mRefPoint,
+ aFrame);
+}
+
+nsPoint
+nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
+ const LayoutDeviceIntPoint& aPoint,
+ nsIFrame* aFrame)
+{
+ if (!aFrame) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsIWidget* widget = aEvent->AsGUIEvent()->mWidget;
+ if (!widget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame);
+}
+
+nsPoint
+nsLayoutUtils::GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPoint,
+ nsIFrame* aFrame)
+{
+ if (!aFrame || !aWidget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsView* view = aFrame->GetView();
+ if (view) {
+ nsIWidget* frameWidget = view->GetWidget();
+ if (frameWidget && frameWidget == aWidget) {
+ // Special case this cause it happens a lot.
+ // This also fixes bug 664707, events in the extra-special case of select
+ // dropdown popups that are transformed.
+ nsPresContext* presContext = aFrame->PresContext();
+ nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
+ presContext->DevPixelsToAppUnits(aPoint.y));
+ pt = pt - view->ViewToWidgetOffset();
+ pt = pt.RemoveResolution(GetCurrentAPZResolutionScale(presContext->PresShell()));
+ return pt;
+ }
+ }
+
+ /* If we walk up the frame tree and discover that any of the frames are
+ * transformed, we need to do extra work to convert from the global
+ * space to the local space.
+ */
+ nsIFrame* rootFrame = aFrame;
+ bool transformFound = false;
+ for (nsIFrame* f = aFrame; f; f = GetCrossDocParentFrame(f)) {
+ if (f->IsTransformed()) {
+ transformFound = true;
+ }
+
+ rootFrame = f;
+ }
+
+ nsView* rootView = rootFrame->GetView();
+ if (!rootView) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsPoint widgetToView = TranslateWidgetToView(rootFrame->PresContext(),
+ aWidget, aPoint, rootView);
+
+ if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ // Convert from root document app units to app units of the document aFrame
+ // is in.
+ int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
+ int32_t localAPD = aFrame->PresContext()->AppUnitsPerDevPixel();
+ widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD);
+ nsIPresShell* shell = aFrame->PresContext()->PresShell();
+
+ // XXX Bug 1224748 - Update nsLayoutUtils functions to correctly handle nsPresShell resolution
+ widgetToView = widgetToView.RemoveResolution(GetCurrentAPZResolutionScale(shell));
+
+ /* If we encountered a transform, we can't do simple arithmetic to figure
+ * out how to convert back to aFrame's coordinates and must use the CTM.
+ */
+ if (transformFound || aFrame->IsSVGText()) {
+ return TransformRootPointToFrame(aFrame, widgetToView);
+ }
+
+ /* Otherwise, all coordinate systems are translations of one another,
+ * so we can just subtract out the difference.
+ */
+ return widgetToView - aFrame->GetOffsetToCrossDoc(rootFrame);
+}
+
+nsIFrame*
+nsLayoutUtils::GetPopupFrameForEventCoordinates(nsPresContext* aPresContext,
+ const WidgetEvent* aEvent)
+{
+#ifdef MOZ_XUL
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return nullptr;
+ }
+ nsTArray<nsIFrame*> popups;
+ pm->GetVisiblePopups(popups);
+ uint32_t i;
+ // Search from top to bottom
+ for (i = 0; i < popups.Length(); i++) {
+ nsIFrame* popup = popups[i];
+ if (popup->PresContext()->GetRootPresContext() == aPresContext &&
+ popup->GetScrollableOverflowRect().Contains(
+ GetEventCoordinatesRelativeTo(aEvent, popup))) {
+ return popup;
+ }
+ }
+#endif
+ return nullptr;
+}
+
+static void ConstrainToCoordValues(float& aStart, float& aSize)
+{
+ MOZ_ASSERT(aSize >= 0);
+
+ // Here we try to make sure that the resulting nsRect will continue to cover
+ // as much of the area that was covered by the original gfx Rect as possible.
+
+ // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since
+ // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this
+ // range:
+ float end = aStart + aSize;
+ aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX));
+ end = clamped(end, float(nscoord_MIN), float(nscoord_MAX));
+
+ aSize = end - aStart;
+
+ // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height()
+ // can't return a value greater than nscoord_MAX. If aSize is greater than
+ // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect
+ // centered:
+ if (aSize > nscoord_MAX) {
+ float excess = aSize - nscoord_MAX;
+ excess /= 2;
+ aStart += excess;
+ aSize = nscoord_MAX;
+ }
+}
+
+/**
+ * Given a gfxFloat, constrains its value to be between nscoord_MIN and nscoord_MAX.
+ *
+ * @param aVal The value to constrain (in/out)
+ */
+static void ConstrainToCoordValues(gfxFloat& aVal)
+{
+ if (aVal <= nscoord_MIN)
+ aVal = nscoord_MIN;
+ else if (aVal >= nscoord_MAX)
+ aVal = nscoord_MAX;
+}
+
+static void ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize)
+{
+ gfxFloat max = aStart + aSize;
+
+ // Clamp the end points to within nscoord range
+ ConstrainToCoordValues(aStart);
+ ConstrainToCoordValues(max);
+
+ aSize = max - aStart;
+ // If the width if still greater than the max nscoord, then bring both
+ // endpoints in by the same amount until it fits.
+ if (aSize > nscoord_MAX) {
+ gfxFloat excess = aSize - nscoord_MAX;
+ excess /= 2;
+
+ aStart += excess;
+ aSize = nscoord_MAX;
+ } else if (aSize < nscoord_MIN) {
+ gfxFloat excess = aSize - nscoord_MIN;
+ excess /= 2;
+
+ aStart -= excess;
+ aSize = nscoord_MIN;
+ }
+}
+
+nsRect
+nsLayoutUtils::RoundGfxRectToAppRect(const Rect &aRect, float aFactor)
+{
+ /* Get a new Rect whose units are app units by scaling by the specified factor. */
+ Rect scaledRect = aRect;
+ scaledRect.ScaleRoundOut(aFactor);
+
+ /* We now need to constrain our results to the max and min values for coords. */
+ ConstrainToCoordValues(scaledRect.x, scaledRect.width);
+ ConstrainToCoordValues(scaledRect.y, scaledRect.height);
+
+ /* Now typecast everything back. This is guaranteed to be safe. */
+ return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()),
+ nscoord(scaledRect.Width()), nscoord(scaledRect.Height()));
+}
+
+nsRect
+nsLayoutUtils::RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor)
+{
+ /* Get a new gfxRect whose units are app units by scaling by the specified factor. */
+ gfxRect scaledRect = aRect;
+ scaledRect.ScaleRoundOut(aFactor);
+
+ /* We now need to constrain our results to the max and min values for coords. */
+ ConstrainToCoordValues(scaledRect.x, scaledRect.width);
+ ConstrainToCoordValues(scaledRect.y, scaledRect.height);
+
+ /* Now typecast everything back. This is guaranteed to be safe. */
+ return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()),
+ nscoord(scaledRect.Width()), nscoord(scaledRect.Height()));
+}
+
+
+nsRegion
+nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect)
+{
+ // rectFullHeight and rectFullWidth together will approximately contain
+ // the total area of the frame minus the rounded corners.
+ nsRect rectFullHeight = aRoundedRect;
+ nscoord xDiff = std::max(aRadii[NS_CORNER_TOP_LEFT_X], aRadii[NS_CORNER_BOTTOM_LEFT_X]);
+ rectFullHeight.x += xDiff;
+ rectFullHeight.width -= std::max(aRadii[NS_CORNER_TOP_RIGHT_X],
+ aRadii[NS_CORNER_BOTTOM_RIGHT_X]) + xDiff;
+ nsRect r1;
+ r1.IntersectRect(rectFullHeight, aContainedRect);
+
+ nsRect rectFullWidth = aRoundedRect;
+ nscoord yDiff = std::max(aRadii[NS_CORNER_TOP_LEFT_Y], aRadii[NS_CORNER_TOP_RIGHT_Y]);
+ rectFullWidth.y += yDiff;
+ rectFullWidth.height -= std::max(aRadii[NS_CORNER_BOTTOM_LEFT_Y],
+ aRadii[NS_CORNER_BOTTOM_RIGHT_Y]) + yDiff;
+ nsRect r2;
+ r2.IntersectRect(rectFullWidth, aContainedRect);
+
+ nsRegion result;
+ result.Or(r1, r2);
+ return result;
+}
+
+nsIntRegion
+nsLayoutUtils::RoundedRectIntersectIntRect(const nsIntRect& aRoundedRect,
+ const RectCornerRadii& aCornerRadii,
+ const nsIntRect& aContainedRect)
+{
+ // rectFullHeight and rectFullWidth together will approximately contain
+ // the total area of the frame minus the rounded corners.
+ nsIntRect rectFullHeight = aRoundedRect;
+ uint32_t xDiff = std::max(aCornerRadii.TopLeft().width,
+ aCornerRadii.BottomLeft().width);
+ rectFullHeight.x += xDiff;
+ rectFullHeight.width -= std::max(aCornerRadii.TopRight().width,
+ aCornerRadii.BottomRight().width) + xDiff;
+ nsIntRect r1;
+ r1.IntersectRect(rectFullHeight, aContainedRect);
+
+ nsIntRect rectFullWidth = aRoundedRect;
+ uint32_t yDiff = std::max(aCornerRadii.TopLeft().height,
+ aCornerRadii.TopRight().height);
+ rectFullWidth.y += yDiff;
+ rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height,
+ aCornerRadii.BottomRight().height) + yDiff;
+ nsIntRect r2;
+ r2.IntersectRect(rectFullWidth, aContainedRect);
+
+ nsIntRegion result;
+ result.Or(r1, r2);
+ return result;
+}
+
+// Helper for RoundedRectIntersectsRect.
+static bool
+CheckCorner(nscoord aXOffset, nscoord aYOffset,
+ nscoord aXRadius, nscoord aYRadius)
+{
+ MOZ_ASSERT(aXOffset > 0 && aYOffset > 0,
+ "must not pass nonpositives to CheckCorner");
+ MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0,
+ "must not pass negatives to CheckCorner");
+
+ // Avoid floating point math unless we're either (1) within the
+ // quarter-ellipse area at the rounded corner or (2) outside the
+ // rounding.
+ if (aXOffset >= aXRadius || aYOffset >= aYRadius)
+ return true;
+
+ // Convert coordinates to a unit circle with (0,0) as the center of
+ // curvature, and see if we're inside the circle or outside.
+ float scaledX = float(aXRadius - aXOffset) / float(aXRadius);
+ float scaledY = float(aYRadius - aYOffset) / float(aYRadius);
+ return scaledX * scaledX + scaledY * scaledY < 1.0f;
+}
+
+bool
+nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aTestRect)
+{
+ if (!aTestRect.Intersects(aRoundedRect))
+ return false;
+
+ // distances from this edge of aRoundedRect to opposite edge of aTestRect,
+ // which we know are positive due to the Intersects check above.
+ nsMargin insets;
+ insets.top = aTestRect.YMost() - aRoundedRect.y;
+ insets.right = aRoundedRect.XMost() - aTestRect.x;
+ insets.bottom = aRoundedRect.YMost() - aTestRect.y;
+ insets.left = aTestRect.XMost() - aRoundedRect.x;
+
+ // Check whether the bottom-right corner of aTestRect is inside the
+ // top left corner of aBounds when rounded by aRadii, etc. If any
+ // corner is not, then fail; otherwise succeed.
+ return CheckCorner(insets.left, insets.top,
+ aRadii[NS_CORNER_TOP_LEFT_X],
+ aRadii[NS_CORNER_TOP_LEFT_Y]) &&
+ CheckCorner(insets.right, insets.top,
+ aRadii[NS_CORNER_TOP_RIGHT_X],
+ aRadii[NS_CORNER_TOP_RIGHT_Y]) &&
+ CheckCorner(insets.right, insets.bottom,
+ aRadii[NS_CORNER_BOTTOM_RIGHT_X],
+ aRadii[NS_CORNER_BOTTOM_RIGHT_Y]) &&
+ CheckCorner(insets.left, insets.bottom,
+ aRadii[NS_CORNER_BOTTOM_LEFT_X],
+ aRadii[NS_CORNER_BOTTOM_LEFT_Y]);
+}
+
+nsRect
+nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds,
+ const Matrix4x4 &aMatrix, float aFactor)
+{
+ RectDouble image = RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.y, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.width, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.height, aFactor));
+
+ RectDouble maxBounds = RectDouble(double(nscoord_MIN) / aFactor * 0.5,
+ double(nscoord_MIN) / aFactor * 0.5,
+ double(nscoord_MAX) / aFactor,
+ double(nscoord_MAX) / aFactor);
+
+ image = aMatrix.TransformAndClipBounds(image, maxBounds);
+
+ return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
+}
+
+nsPoint
+nsLayoutUtils::MatrixTransformPoint(const nsPoint &aPoint,
+ const Matrix4x4 &aMatrix, float aFactor)
+{
+ gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor),
+ NSAppUnitsToFloatPixels(aPoint.y, aFactor));
+ image.Transform(aMatrix);
+ return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor),
+ NSFloatPixelsToAppUnits(float(image.y), aFactor));
+}
+
+void
+nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin, float aAppUnitsPerPixel, bool aRounded)
+{
+ Point3D gfxOrigin =
+ Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel),
+ 0.0f);
+ if (aRounded) {
+ gfxOrigin.x = NS_round(gfxOrigin.x);
+ gfxOrigin.y = NS_round(gfxOrigin.y);
+ }
+ aTransform.PostTranslate(gfxOrigin);
+}
+
+Matrix4x4
+nsLayoutUtils::GetTransformToAncestor(nsIFrame *aFrame, const nsIFrame *aAncestor)
+{
+ nsIFrame* parent;
+ Matrix4x4 ctm;
+ if (aFrame == aAncestor) {
+ return ctm;
+ }
+ ctm = aFrame->GetTransformMatrix(aAncestor, &parent);
+ while (parent && parent != aAncestor) {
+ if (!parent->Extend3DContext()) {
+ ctm.ProjectTo2D();
+ }
+ ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent);
+ }
+ return ctm;
+}
+
+gfxSize
+nsLayoutUtils::GetTransformToAncestorScale(nsIFrame* aFrame)
+{
+ Matrix4x4 transform = GetTransformToAncestor(aFrame,
+ nsLayoutUtils::GetDisplayRootFrame(aFrame));
+ Matrix transform2D;
+ if (transform.Is2D(&transform2D)) {
+ return ThebesMatrix(transform2D).ScaleFactors(true);
+ }
+ return gfxSize(1, 1);
+}
+
+static Matrix4x4
+GetTransformToAncestorExcludingAnimated(nsIFrame* aFrame,
+ const nsIFrame* aAncestor)
+{
+ nsIFrame* parent;
+ Matrix4x4 ctm;
+ if (aFrame == aAncestor) {
+ return ctm;
+ }
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) {
+ return ctm;
+ }
+ ctm = aFrame->GetTransformMatrix(aAncestor, &parent);
+ while (parent && parent != aAncestor) {
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) {
+ return Matrix4x4();
+ }
+ if (!parent->Extend3DContext()) {
+ ctm.ProjectTo2D();
+ }
+ ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent);
+ }
+ return ctm;
+}
+
+gfxSize
+nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(nsIFrame* aFrame)
+{
+ Matrix4x4 transform = GetTransformToAncestorExcludingAnimated(aFrame,
+ nsLayoutUtils::GetDisplayRootFrame(aFrame));
+ Matrix transform2D;
+ if (transform.Is2D(&transform2D)) {
+ return ThebesMatrix(transform2D).ScaleFactors(true);
+ }
+ return gfxSize(1, 1);
+}
+
+nsIFrame*
+nsLayoutUtils::FindNearestCommonAncestorFrame(nsIFrame* aFrame1, nsIFrame* aFrame2)
+{
+ AutoTArray<nsIFrame*,100> ancestors1;
+ AutoTArray<nsIFrame*,100> ancestors2;
+ nsIFrame* commonAncestor = nullptr;
+ if (aFrame1->PresContext() == aFrame2->PresContext()) {
+ commonAncestor = aFrame1->PresContext()->PresShell()->GetRootFrame();
+ }
+ for (nsIFrame* f = aFrame1; f != commonAncestor;
+ f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ ancestors1.AppendElement(f);
+ }
+ for (nsIFrame* f = aFrame2; f != commonAncestor;
+ f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ ancestors2.AppendElement(f);
+ }
+ uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length());
+ for (uint32_t i = 1; i <= minLengths; ++i) {
+ if (ancestors1[ancestors1.Length() - i] == ancestors2[ancestors2.Length() - i]) {
+ commonAncestor = ancestors1[ancestors1.Length() - i];
+ } else {
+ break;
+ }
+ }
+ return commonAncestor;
+}
+
+nsLayoutUtils::TransformResult
+nsLayoutUtils::TransformPoints(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ uint32_t aPointCount, CSSPoint* aPoints)
+{
+ nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
+ if (!nearestCommonAncestor) {
+ return NO_COMMON_ANCESTOR;
+ }
+ Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor);
+ if (downToDest.IsSingular()) {
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ downToDest.Invert();
+ Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor);
+ CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame =
+ aFromFrame->PresContext()->CSSToDevPixelScale();
+ CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame =
+ aToFrame->PresContext()->CSSToDevPixelScale();
+ for (uint32_t i = 0; i < aPointCount; ++i) {
+ LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame;
+ // What should the behaviour be if some of the points aren't invertible
+ // and others are? Just assume all points are for now.
+ Point toDevPixels = downToDest.ProjectPoint(
+ (upToAncestor.TransformPoint(Point(devPixels.x, devPixels.y)))).As2DPoint();
+ // Divide here so that when the devPixelsPerCSSPixels are the same, we get the correct
+ // answer instead of some inaccuracy multiplying a number by its reciprocal.
+ aPoints[i] = LayoutDevicePoint(toDevPixels.x, toDevPixels.y) /
+ devPixelsPerCSSPixelToFrame;
+ }
+ return TRANSFORM_SUCCEEDED;
+}
+
+nsLayoutUtils::TransformResult
+nsLayoutUtils::TransformPoint(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ nsPoint& aPoint)
+{
+ nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
+ if (!nearestCommonAncestor) {
+ return NO_COMMON_ANCESTOR;
+ }
+ Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor);
+ if (downToDest.IsSingular()) {
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ downToDest.Invert();
+ Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor);
+
+ float devPixelsPerAppUnitFromFrame =
+ 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel();
+ float devPixelsPerAppUnitToFrame =
+ 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel();
+ Point4D toDevPixels = downToDest.ProjectPoint(
+ upToAncestor.TransformPoint(Point(aPoint.x * devPixelsPerAppUnitFromFrame,
+ aPoint.y * devPixelsPerAppUnitFromFrame)));
+ if (!toDevPixels.HasPositiveWCoord()) {
+ // Not strictly true, but we failed to get a valid point in this
+ // coordinate space.
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ aPoint.x = NSToCoordRound(toDevPixels.x / devPixelsPerAppUnitToFrame);
+ aPoint.y = NSToCoordRound(toDevPixels.y / devPixelsPerAppUnitToFrame);
+ return TRANSFORM_SUCCEEDED;
+}
+
+nsLayoutUtils::TransformResult
+nsLayoutUtils::TransformRect(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ nsRect& aRect)
+{
+ nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
+ if (!nearestCommonAncestor) {
+ return NO_COMMON_ANCESTOR;
+ }
+ Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor);
+ if (downToDest.IsSingular()) {
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ downToDest.Invert();
+ Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor);
+
+ float devPixelsPerAppUnitFromFrame =
+ 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel();
+ float devPixelsPerAppUnitToFrame =
+ 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel();
+ gfx::Rect toDevPixels = downToDest.ProjectRectBounds(
+ upToAncestor.ProjectRectBounds(
+ gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame,
+ aRect.y * devPixelsPerAppUnitFromFrame,
+ aRect.width * devPixelsPerAppUnitFromFrame,
+ aRect.height * devPixelsPerAppUnitFromFrame),
+ Rect(-std::numeric_limits<Float>::max() * 0.5f,
+ -std::numeric_limits<Float>::max() * 0.5f,
+ std::numeric_limits<Float>::max(),
+ std::numeric_limits<Float>::max())),
+ Rect(-std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame * 0.5f,
+ -std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame * 0.5f,
+ std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame,
+ std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame));
+ aRect.x = toDevPixels.x / devPixelsPerAppUnitToFrame;
+ aRect.y = toDevPixels.y / devPixelsPerAppUnitToFrame;
+ aRect.width = toDevPixels.width / devPixelsPerAppUnitToFrame;
+ aRect.height = toDevPixels.height / devPixelsPerAppUnitToFrame;
+ return TRANSFORM_SUCCEEDED;
+}
+
+nsRect
+nsLayoutUtils::GetRectRelativeToFrame(Element* aElement, nsIFrame* aFrame)
+{
+ if (!aElement || !aFrame) {
+ return nsRect();
+ }
+
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ return nsRect();
+ }
+
+ nsRect rect = frame->GetRectRelativeToSelf();
+ nsLayoutUtils::TransformResult rv =
+ nsLayoutUtils::TransformRect(frame, aFrame, rect);
+ if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return nsRect();
+ }
+
+ return rect;
+}
+
+bool
+nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
+ nscoord aInflateSize)
+{
+ nsRect rect = aRect;
+ rect.Inflate(aInflateSize);
+ return rect.Contains(aPoint);
+}
+
+nsRect
+nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame, const nsRect& aRect)
+{
+ nsIFrame* closestScrollFrame =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
+
+ nsRect resultRect = aRect;
+
+ while (closestScrollFrame) {
+ nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
+
+ nsRect scrollPortRect = sf->GetScrollPortRect();
+ nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect);
+
+ resultRect = resultRect.Intersect(scrollPortRect);
+
+ // Check whether aRect is visible in the scroll frame or not.
+ if (resultRect.IsEmpty()) {
+ break;
+ }
+
+ // Get next ancestor scroll frame.
+ closestScrollFrame =
+ nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
+ nsGkAtoms::scrollFrame);
+ }
+
+ return resultRect;
+}
+
+bool
+nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame,
+ Matrix4x4* aTransform)
+{
+ // FIXME/bug 796690: we can sometimes compute a transform in these
+ // cases, it just increases complexity considerably. Punt for now.
+ if (aFrame->Extend3DContext() || aFrame->HasTransformGetter()) {
+ return false;
+ }
+
+ nsIFrame* root = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ if (root->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) {
+ // Content may have been invalidated, so we can't reliably compute
+ // the "layer transform" in general.
+ return false;
+ }
+ // If the caller doesn't care about the value, early-return to skip
+ // overhead below.
+ if (!aTransform) {
+ return true;
+ }
+
+ nsDisplayListBuilder builder(root,
+ nsDisplayListBuilderMode::TRANSFORM_COMPUTATION,
+ false/*don't build caret*/);
+ nsDisplayList list;
+ nsDisplayTransform* item =
+ new (&builder) nsDisplayTransform(&builder, aFrame, &list, nsRect());
+
+ *aTransform = item->GetTransform();
+ item->~nsDisplayTransform();
+
+ return true;
+}
+
+static bool
+TransformGfxPointFromAncestor(nsIFrame *aFrame,
+ const Point &aPoint,
+ nsIFrame *aAncestor,
+ Point* aOut)
+{
+ Matrix4x4 ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor);
+ ctm.Invert();
+ Point4D point = ctm.ProjectPoint(aPoint);
+ if (!point.HasPositiveWCoord()) {
+ return false;
+ }
+ *aOut = point.As2DPoint();
+ return true;
+}
+
+static Rect
+TransformGfxRectToAncestor(nsIFrame *aFrame,
+ const Rect &aRect,
+ const nsIFrame *aAncestor,
+ bool* aPreservesAxisAlignedRectangles = nullptr,
+ Maybe<Matrix4x4>* aMatrixCache = nullptr)
+{
+ Matrix4x4 ctm;
+ if (aMatrixCache && *aMatrixCache) {
+ // We are given a matrix to use, so use it
+ ctm = aMatrixCache->value();
+ } else {
+ // Else, compute it
+ ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor);
+ if (aMatrixCache) {
+ // and put it in the cache, if provided
+ *aMatrixCache = Some(ctm);
+ }
+ }
+ // Fill out the axis-alignment flag
+ if (aPreservesAxisAlignedRectangles) {
+ Matrix matrix2d;
+ *aPreservesAxisAlignedRectangles =
+ ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles();
+ }
+ Rect maxBounds = Rect(-std::numeric_limits<float>::max() * 0.5,
+ -std::numeric_limits<float>::max() * 0.5,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max());
+ return ctm.TransformAndClipBounds(aRect, maxBounds);
+}
+
+static SVGTextFrame*
+GetContainingSVGTextFrame(nsIFrame* aFrame)
+{
+ if (!aFrame->IsSVGText()) {
+ return nullptr;
+ }
+
+ return static_cast<SVGTextFrame*>
+ (nsLayoutUtils::GetClosestFrameOfType(aFrame->GetParent(),
+ nsGkAtoms::svgTextFrame));
+}
+
+nsPoint
+nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ nsIFrame* aAncestor)
+{
+ SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
+
+ float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Point result(NSAppUnitsToFloatPixels(aPoint.x, factor),
+ NSAppUnitsToFloatPixels(aPoint.y, factor));
+
+ if (text) {
+ if (!TransformGfxPointFromAncestor(text, result, aAncestor, &result)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+ result = text->TransformFramePointToTextChild(result, aFrame);
+ } else {
+ if (!TransformGfxPointFromAncestor(aFrame, result, nullptr, &result)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+ }
+
+ return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
+ NSFloatPixelsToAppUnits(float(result.y), factor));
+}
+
+nsRect
+nsLayoutUtils::TransformFrameRectToAncestor(nsIFrame* aFrame,
+ const nsRect& aRect,
+ const nsIFrame* aAncestor,
+ bool* aPreservesAxisAlignedRectangles /* = nullptr */,
+ Maybe<Matrix4x4>* aMatrixCache /* = nullptr */)
+{
+ SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
+
+ float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect result;
+
+ if (text) {
+ result = ToRect(text->TransformFrameRectFromTextChild(aRect, aFrame));
+ result = TransformGfxRectToAncestor(text, result, aAncestor, nullptr, aMatrixCache);
+ // TransformFrameRectFromTextChild could involve any kind of transform, we
+ // could drill down into it to get an answer out of it but we don't yet.
+ if (aPreservesAxisAlignedRectangles)
+ *aPreservesAxisAlignedRectangles = false;
+ } else {
+ result = Rect(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel));
+ result = TransformGfxRectToAncestor(aFrame, result, aAncestor, aPreservesAxisAlignedRectangles, aMatrixCache);
+ }
+
+ float destAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel();
+ return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel));
+}
+
+static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget, nsIWidget*& aRootWidget) {
+ LayoutDeviceIntPoint offset(0, 0);
+ while ((aWidget->WindowType() == eWindowType_child ||
+ aWidget->IsPlugin())) {
+ nsIWidget* parent = aWidget->GetParent();
+ if (!parent) {
+ break;
+ }
+ LayoutDeviceIntRect bounds = aWidget->GetBounds();
+ offset += bounds.TopLeft();
+ aWidget = parent;
+ }
+ aRootWidget = aWidget;
+ return offset;
+}
+
+static LayoutDeviceIntPoint
+WidgetToWidgetOffset(nsIWidget* aFrom, nsIWidget* aTo) {
+ nsIWidget* fromRoot;
+ LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot);
+ nsIWidget* toRoot;
+ LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot);
+
+ if (fromRoot == toRoot) {
+ return fromOffset - toOffset;
+ }
+ return aFrom->WidgetToScreenOffset() - aTo->WidgetToScreenOffset();
+}
+
+nsPoint
+nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext,
+ nsIWidget* aWidget, const LayoutDeviceIntPoint& aPt,
+ nsView* aView)
+{
+ nsPoint viewOffset;
+ nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
+ if (!viewWidget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ LayoutDeviceIntPoint widgetPoint = aPt + WidgetToWidgetOffset(aWidget, viewWidget);
+ nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x),
+ aPresContext->DevPixelsToAppUnits(widgetPoint.y));
+ return widgetAppUnits - viewOffset;
+}
+
+LayoutDeviceIntPoint
+nsLayoutUtils::TranslateViewToWidget(nsPresContext* aPresContext,
+ nsView* aView, nsPoint aPt,
+ nsIWidget* aWidget)
+{
+ nsPoint viewOffset;
+ nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
+ if (!viewWidget) {
+ return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsPoint pt = (aPt +
+ viewOffset).ApplyResolution(GetCurrentAPZResolutionScale(aPresContext->PresShell()));
+ LayoutDeviceIntPoint relativeToViewWidget(aPresContext->AppUnitsToDevPixels(pt.x),
+ aPresContext->AppUnitsToDevPixels(pt.y));
+ return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget);
+}
+
+// Combine aNewBreakType with aOrigBreakType, but limit the break types
+// to StyleClear::Left, Right, Both.
+StyleClear
+nsLayoutUtils::CombineBreakType(StyleClear aOrigBreakType,
+ StyleClear aNewBreakType)
+{
+ StyleClear breakType = aOrigBreakType;
+ switch(breakType) {
+ case StyleClear::Left:
+ if (StyleClear::Right == aNewBreakType ||
+ StyleClear::Both == aNewBreakType) {
+ breakType = StyleClear::Both;
+ }
+ break;
+ case StyleClear::Right:
+ if (StyleClear::Left == aNewBreakType ||
+ StyleClear::Both == aNewBreakType) {
+ breakType = StyleClear::Both;
+ }
+ break;
+ case StyleClear::None:
+ if (StyleClear::Left == aNewBreakType ||
+ StyleClear::Right == aNewBreakType ||
+ StyleClear::Both == aNewBreakType) {
+ breakType = aNewBreakType;
+ }
+ break;
+ default:
+ break;
+ }
+ return breakType;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+#include <stdio.h>
+
+static bool gDumpEventList = false;
+
+// nsLayoutUtils::PaintFrame() can call itself recursively, so rather than
+// maintaining a single paint count, we need a stack.
+StaticAutoPtr<nsTArray<int>> gPaintCountStack;
+
+struct AutoNestedPaintCount {
+ AutoNestedPaintCount() {
+ gPaintCountStack->AppendElement(0);
+ }
+ ~AutoNestedPaintCount() {
+ gPaintCountStack->RemoveElementAt(gPaintCountStack->Length() - 1);
+ }
+};
+
+#endif
+
+nsIFrame*
+nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt, uint32_t aFlags)
+{
+ PROFILER_LABEL("nsLayoutUtils", "GetFrameForPoint",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ nsresult rv;
+ AutoTArray<nsIFrame*,8> outFrames;
+ rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aFlags);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return outFrames.Length() ? outFrames.ElementAt(0) : nullptr;
+}
+
+nsresult
+nsLayoutUtils::GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
+ nsTArray<nsIFrame*> &aOutFrames,
+ uint32_t aFlags)
+{
+ PROFILER_LABEL("nsLayoutUtils", "GetFramesForArea",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ nsDisplayListBuilder builder(aFrame,
+ nsDisplayListBuilderMode::EVENT_DELIVERY,
+ false);
+ nsDisplayList list;
+
+ if (aFlags & IGNORE_PAINT_SUPPRESSION) {
+ builder.IgnorePaintSuppression();
+ }
+
+ if (aFlags & IGNORE_ROOT_SCROLL_FRAME) {
+ nsIFrame* rootScrollFrame =
+ aFrame->PresContext()->PresShell()->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ builder.SetIgnoreScrollFrame(rootScrollFrame);
+ }
+ }
+ if (aFlags & IGNORE_CROSS_DOC) {
+ builder.SetDescendIntoSubdocuments(false);
+ }
+
+ builder.EnterPresShell(aFrame);
+ aFrame->BuildDisplayListForStackingContext(&builder, aRect, &list);
+ builder.LeavePresShell(aFrame, nullptr);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gDumpEventList) {
+ fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y);
+
+ std::stringstream ss;
+ nsFrame::PrintDisplayList(&builder, list, ss);
+ print_stderr(ss);
+ }
+#endif
+
+ nsDisplayItem::HitTestState hitTestState;
+ list.HitTest(&builder, aRect, &hitTestState, &aOutFrames);
+ list.DeleteAll();
+ return NS_OK;
+}
+
+// aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame
+FrameMetrics
+nsLayoutUtils::CalculateBasicFrameMetrics(nsIScrollableFrame* aScrollFrame) {
+ nsIFrame* frame = do_QueryFrame(aScrollFrame);
+ MOZ_ASSERT(frame);
+
+ // Calculate the metrics necessary for calculating the displayport.
+ // This code has a lot in common with the code in ComputeFrameMetrics();
+ // we may want to refactor this at some point.
+ FrameMetrics metrics;
+ nsPresContext* presContext = frame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+ CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale();
+ float resolution = 1.0f;
+ if (frame == presShell->GetRootScrollFrame()) {
+ // Only the root scrollable frame for a given presShell should pick up
+ // the presShell's resolution. All the other frames are 1.0.
+ resolution = presShell->GetResolution();
+ }
+ // Note: unlike in ComputeFrameMetrics(), we don't know the full cumulative
+ // resolution including FrameMetrics::mExtraResolution, because layout hasn't
+ // chosen a resolution to paint at yet. However, the display port calculation
+ // divides out mExtraResolution anyways, so we get the correct result by
+ // setting the mCumulativeResolution to everything except the extra resolution
+ // and leaving mExtraResolution at 1.
+ LayoutDeviceToLayerScale2D cumulativeResolution(
+ presShell->GetCumulativeResolution()
+ * nsLayoutUtils::GetTransformToAncestorScale(frame));
+
+ LayerToParentLayerScale layerToParentLayerScale(1.0f);
+ metrics.SetDevPixelsPerCSSPixel(deviceScale);
+ metrics.SetPresShellResolution(resolution);
+ metrics.SetCumulativeResolution(cumulativeResolution);
+ metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale);
+
+ // Only the size of the composition bounds is relevant to the
+ // displayport calculation, not its origin.
+ nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(frame);
+ LayoutDeviceToParentLayerScale2D compBoundsScale;
+ if (frame == presShell->GetRootScrollFrame() && presContext->IsRootContentDocument()) {
+ if (presContext->GetParentPresContext()) {
+ float res = presContext->GetParentPresContext()->PresShell()->GetCumulativeResolution();
+ compBoundsScale = LayoutDeviceToParentLayerScale2D(
+ LayoutDeviceToParentLayerScale(res));
+ }
+ } else {
+ compBoundsScale = cumulativeResolution * layerToParentLayerScale;
+ }
+ metrics.SetCompositionBounds(
+ LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize),
+ presContext->AppUnitsPerDevPixel())
+ * compBoundsScale);
+
+ metrics.SetRootCompositionSize(
+ nsLayoutUtils::CalculateRootCompositionSize(frame, false, metrics));
+
+ metrics.SetScrollOffset(CSSPoint::FromAppUnits(
+ aScrollFrame->GetScrollPosition()));
+
+ metrics.SetScrollableRect(CSSRect::FromAppUnits(
+ nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr)));
+
+ return metrics;
+}
+
+bool
+nsLayoutUtils::CalculateAndSetDisplayPortMargins(nsIScrollableFrame* aScrollFrame,
+ RepaintMode aRepaintMode) {
+ nsIFrame* frame = do_QueryFrame(aScrollFrame);
+ MOZ_ASSERT(frame);
+ nsIContent* content = frame->GetContent();
+ MOZ_ASSERT(content);
+
+ FrameMetrics metrics = CalculateBasicFrameMetrics(aScrollFrame);
+ ScreenMargin displayportMargins = APZCTreeManager::CalculatePendingDisplayPort(
+ metrics, ParentLayerPoint(0.0f, 0.0f));
+ nsIPresShell* presShell = frame->PresContext()->GetPresShell();
+ return nsLayoutUtils::SetDisplayPortMargins(
+ content, presShell, displayportMargins, 0, aRepaintMode);
+}
+
+void
+nsLayoutUtils::MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder,
+ nsIFrame* aScrollFrame) {
+ nsIContent* content = aScrollFrame->GetContent();
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame);
+ if (!content || !scrollableFrame) {
+ return;
+ }
+
+ bool haveDisplayPort = HasDisplayPort(content);
+
+ // 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.
+ if (aBuilder.IsPaintingToWindow() &&
+ nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame) &&
+ !aBuilder.HaveScrollableDisplayPort() &&
+ scrollableFrame->WantAsyncScroll()) {
+
+ // If we don't already have a displayport, calculate and set one.
+ if (!haveDisplayPort) {
+ CalculateAndSetDisplayPortMargins(scrollableFrame, nsLayoutUtils::RepaintMode::DoNotRepaint);
+#ifdef DEBUG
+ haveDisplayPort = HasDisplayPort(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();
+ }
+}
+
+nsIScrollableFrame*
+nsLayoutUtils::GetAsyncScrollableAncestorFrame(nsIFrame* aTarget)
+{
+ uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT
+ | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE
+ | nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT;
+ return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags);
+}
+
+void
+nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame,
+ RepaintMode aRepaintMode)
+{
+ nsIFrame* frame = aFrame;
+ while (frame) {
+ frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
+ if (!frame) {
+ break;
+ }
+ nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame);
+ if (!scrollAncestor) {
+ break;
+ }
+ frame = do_QueryFrame(scrollAncestor);
+ MOZ_ASSERT(frame);
+ MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
+ frame->PresContext()->PresShell()->GetRootScrollFrame() == frame);
+ if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
+ !nsLayoutUtils::HasDisplayPort(frame->GetContent())) {
+ nsLayoutUtils::SetDisplayPortMargins(
+ frame->GetContent(), frame->PresContext()->PresShell(), ScreenMargin(), 0,
+ aRepaintMode);
+ }
+ }
+}
+
+void
+nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame)
+{
+ nsIFrame* frame = aFrame;
+ while (frame) {
+ frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
+ if (!frame) {
+ break;
+ }
+ nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame);
+ if (!scrollAncestor) {
+ break;
+ }
+ frame = do_QueryFrame(scrollAncestor);
+ MOZ_ASSERT(frame);
+ if (!frame) {
+ break;
+ }
+ MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
+ frame->PresContext()->PresShell()->GetRootScrollFrame() == frame);
+ if (nsLayoutUtils::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;
+ }
+ }
+}
+
+nsresult
+nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame,
+ const nsRegion& aDirtyRegion, nscolor aBackstop,
+ nsDisplayListBuilderMode aBuilderMode,
+ PaintFrameFlags aFlags)
+{
+ PROFILER_LABEL("nsLayoutUtils", "PaintFrame",
+ js::ProfileEntry::Category::GRAPHICS);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (!gPaintCountStack) {
+ gPaintCountStack = new nsTArray<int>();
+ ClearOnShutdown(&gPaintCountStack);
+
+ gPaintCountStack->AppendElement(0);
+ }
+ ++gPaintCountStack->LastElement();
+ AutoNestedPaintCount nestedPaintCount;
+#endif
+
+ if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) {
+ nsView* view = aFrame->GetView();
+ if (!(view && view->GetWidget() && GetDisplayRootFrame(aFrame) == aFrame)) {
+ aFlags &= ~PaintFrameFlags::PAINT_WIDGET_LAYERS;
+ NS_ASSERTION(aRenderingContext, "need a rendering context");
+ }
+ }
+
+ nsPresContext* presContext = aFrame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+ nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+ if (!rootPresContext) {
+ return NS_OK;
+ }
+
+ TimeStamp startBuildDisplayList = TimeStamp::Now();
+ nsDisplayListBuilder builder(aFrame, aBuilderMode,
+ !(aFlags & PaintFrameFlags::PAINT_HIDE_CARET));
+ if (aFlags & PaintFrameFlags::PAINT_IN_TRANSFORM) {
+ builder.SetInTransform(true);
+ }
+ if (aFlags & PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES) {
+ builder.SetSyncDecodeImages(true);
+ }
+ if (aFlags & (PaintFrameFlags::PAINT_WIDGET_LAYERS |
+ PaintFrameFlags::PAINT_TO_WINDOW)) {
+ builder.SetPaintingToWindow(true);
+ }
+ if (aFlags & PaintFrameFlags::PAINT_IGNORE_SUPPRESSION) {
+ builder.IgnorePaintSuppression();
+ }
+
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame && !aFrame->GetParent()) {
+ nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable();
+ MOZ_ASSERT(rootScrollableFrame);
+ nsRect displayPortBase = aFrame->GetVisualOverflowRectRelativeToSelf();
+ Unused << rootScrollableFrame->DecideScrollableLayer(&builder, &displayPortBase,
+ /* aAllowCreateDisplayPort = */ true);
+ }
+
+ nsRegion visibleRegion;
+ if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) {
+ // This layer tree will be reused, so we'll need to calculate it
+ // for the whole "visible" area of the window
+ //
+ // |ignoreViewportScrolling| and |usingDisplayPort| are persistent
+ // document-rendering state. We rely on PresShell to flush
+ // retained layers as needed when that persistent state changes.
+ visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf();
+ } else {
+ visibleRegion = aDirtyRegion;
+ }
+
+ nsDisplayList list;
+
+ // If the root has embedded plugins, flag the builder so we know we'll need
+ // to update plugin geometry after painting.
+ if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) &&
+ !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) &&
+ rootPresContext->NeedToComputePluginGeometryUpdates()) {
+ builder.SetWillComputePluginGeometry(true);
+ }
+
+ nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
+ bool ignoreViewportScrolling =
+ aFrame->GetParent() ? false : presShell->IgnoringViewportScrolling();
+ if (ignoreViewportScrolling && rootScrollFrame) {
+ nsIScrollableFrame* rootScrollableFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ if (aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) {
+ // Make visibleRegion and aRenderingContext relative to the
+ // scrolled frame instead of the root frame.
+ nsPoint pos = rootScrollableFrame->GetScrollPosition();
+ visibleRegion.MoveBy(-pos);
+ if (aRenderingContext) {
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(pos,
+ presContext->AppUnitsPerDevPixel());
+ aRenderingContext->ThebesContext()->SetMatrix(
+ aRenderingContext->ThebesContext()->CurrentMatrix().Translate(devPixelOffset));
+ }
+ }
+ builder.SetIgnoreScrollFrame(rootScrollFrame);
+
+ nsCanvasFrame* canvasFrame =
+ do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
+ if (canvasFrame) {
+ // Use UnionRect here to ensure that areas where the scrollbars
+ // were are still filled with the background color.
+ canvasArea.UnionRect(canvasArea,
+ canvasFrame->CanvasArea() + builder.ToReferenceFrame(canvasFrame));
+ }
+ }
+
+ builder.EnterPresShell(aFrame);
+ nsRect dirtyRect = visibleRegion.GetBounds();
+ {
+ // If a scrollable container layer is created in nsDisplayList::PaintForFrame,
+ // it will be the scroll parent for display items that are built in the
+ // BuildDisplayListForStackingContext call below. We need to set the scroll
+ // parent on the display list builder while we build those items, so that they
+ // can pick up their scroll parent's id.
+ ViewID id = FrameMetrics::NULL_SCROLL_ID;
+ if (ignoreViewportScrolling && presContext->IsRootContentDocument()) {
+ if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+ if (nsIContent* content = rootScrollFrame->GetContent()) {
+ id = nsLayoutUtils::FindOrCreateIDFor(content);
+ }
+ }
+ }
+ else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument()
+ && !presShell->GetRootScrollFrame()) {
+ // In cases where the root document is a XUL document, we want to take
+ // the ViewID from the root element, as that will be the ViewID of the
+ // root APZC in the tree. Skip doing this in cases where we know
+ // nsGfxScrollFrame::BuilDisplayList will do it instead.
+ if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) {
+ id = nsLayoutUtils::FindOrCreateIDFor(element);
+ }
+ }
+
+ nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
+
+ PROFILER_LABEL("nsLayoutUtils", "PaintFrame::BuildDisplayList",
+ js::ProfileEntry::Category::GRAPHICS);
+
+
+ PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList);
+ aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list);
+ }
+
+ nsIAtom* frameType = aFrame->GetType();
+
+ // For the viewport frame in print preview/page layout we want to paint
+ // the grey background behind the page, not the canvas color.
+ if (frameType == nsGkAtoms::viewportFrame &&
+ nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) {
+ nsRect bounds = nsRect(builder.ToReferenceFrame(aFrame),
+ aFrame->GetSize());
+ nsDisplayListBuilder::AutoBuildingDisplayList
+ buildingDisplayList(&builder, aFrame, bounds, false);
+ presShell->AddPrintPreviewBackgroundItem(builder, list, aFrame, bounds);
+ } else if (frameType != nsGkAtoms::pageFrame) {
+ // For printing, this function is first called on an nsPageFrame, which
+ // creates a display list with a PageContent item. The PageContent item's
+ // paint function calls this function on the nsPageFrame's child which is
+ // an nsPageContentFrame. We only want to add the canvas background color
+ // item once, for the nsPageContentFrame.
+
+ // Add the canvas background color to the bottom of the list. This
+ // happens after we've built the list so that AddCanvasBackgroundColorItem
+ // can monkey with the contents if necessary.
+ canvasArea.IntersectRect(canvasArea, visibleRegion.GetBounds());
+ nsDisplayListBuilder::AutoBuildingDisplayList
+ buildingDisplayList(&builder, aFrame, canvasArea, false);
+ presShell->AddCanvasBackgroundColorItem(
+ builder, list, aFrame, canvasArea, aBackstop);
+ }
+
+ builder.LeavePresShell(aFrame, &list);
+ Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
+ startBuildDisplayList);
+
+ bool profilerNeedsDisplayList = profiler_feature_active("displaylistdump");
+ bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint();
+#ifdef MOZ_DUMP_PAINTING
+ FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
+#endif
+
+ UniquePtr<std::stringstream> ss;
+ if (consoleNeedsDisplayList || profilerNeedsDisplayList) {
+ ss = MakeUnique<std::stringstream>();
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaintToFile()) {
+ nsCString string("dump-");
+ // Include the process ID in the dump file name, to make sure that in an
+ // e10s setup different processes don't clobber each other's dump files.
+ string.AppendInt(getpid());
+ for (int paintCount : *gPaintCountStack) {
+ string.AppendLiteral("-");
+ string.AppendInt(paintCount);
+ }
+ string.AppendLiteral(".html");
+ gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w");
+ } else {
+ gfxUtils::sDumpPaintFile = stderr;
+ }
+ if (gfxEnv::DumpPaintToFile()) {
+ *ss << "<html><head><script>\n"
+ "var array = {};\n"
+ "function ViewImage(index) { \n"
+ " var image = document.getElementById(index);\n"
+ " if (image.src) {\n"
+ " image.removeAttribute('src');\n"
+ " } else {\n"
+ " image.src = array[index];\n"
+ " }\n"
+ "}</script></head><body>";
+ }
+#endif
+ *ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n",
+ dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height).get();
+ nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile());
+
+ if (gfxEnv::DumpPaint() || gfxEnv::DumpPaintItems()) {
+ // Flush stream now to avoid reordering dump output relative to
+ // messages dumped by PaintRoot below.
+ if (profilerNeedsDisplayList && !consoleNeedsDisplayList) {
+ profiler_log(ss->str().c_str());
+ } else {
+ // Send to the console which will send to the profiler if required.
+ fprint_stderr(gfxUtils::sDumpPaintFile, *ss);
+ }
+ ss = MakeUnique<std::stringstream>();
+ }
+ }
+
+ uint32_t flags = nsDisplayList::PAINT_DEFAULT;
+ if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) {
+ flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
+ if (!(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) {
+ nsIWidget *widget = aFrame->GetNearestWidget();
+ if (widget) {
+ // If we're finished building display list items for painting of the outermost
+ // pres shell, notify the widget about any toolbars we've encountered.
+ widget->UpdateThemeGeometries(builder.GetThemeGeometries());
+ }
+ }
+ }
+ if (aFlags & PaintFrameFlags::PAINT_EXISTING_TRANSACTION) {
+ flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION;
+ }
+ if (aFlags & PaintFrameFlags::PAINT_NO_COMPOSITE) {
+ flags |= nsDisplayList::PAINT_NO_COMPOSITE;
+ }
+ if (aFlags & PaintFrameFlags::PAINT_COMPRESSED) {
+ flags |= nsDisplayList::PAINT_COMPRESSED;
+ }
+
+ TimeStamp paintStart = TimeStamp::Now();
+ RefPtr<LayerManager> layerManager
+ = list.PaintRoot(&builder, aRenderingContext, flags);
+ Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME,
+ paintStart);
+
+ if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) {
+ TimeStamp now = TimeStamp::Now();
+ float rasterizeTime = (now - paintStart).ToMilliseconds();
+ uint32_t pixelCount = layerManager->GetAndClearPaintedPixelCount();
+ static std::vector<std::pair<TimeStamp, uint32_t>> history;
+ if (pixelCount) {
+ history.push_back(std::make_pair(now, pixelCount));
+ }
+ uint32_t paintedInLastSecond = 0;
+ for (auto i = history.begin(); i != history.end(); i++) {
+ if ((now - i->first).ToMilliseconds() > 1000.0f) {
+ // more than 1000ms ago, don't count it
+ continue;
+ }
+ if (paintedInLastSecond == 0) {
+ // This is the first one in the last 1000ms, so drop everything earlier
+ history.erase(history.begin(), i);
+ i = history.begin();
+ }
+ paintedInLastSecond += i->second;
+ MOZ_ASSERT(paintedInLastSecond); // all historical pixel counts are > 0
+ }
+ printf_stderr("Painted %u pixels in %fms (%u in the last 1000ms)\n",
+ pixelCount, rasterizeTime, paintedInLastSecond);
+ }
+
+ if (consoleNeedsDisplayList || profilerNeedsDisplayList) {
+ *ss << "Painting --- after optimization:\n";
+ nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile());
+
+ *ss << "Painting --- layer tree:\n";
+ if (layerManager) {
+ FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss,
+ gfxEnv::DumpPaintToFile());
+ }
+
+ if (profilerNeedsDisplayList && !consoleNeedsDisplayList) {
+ profiler_log(ss->str().c_str());
+ } else {
+ // Send to the console which will send to the profiler if required.
+ fprint_stderr(gfxUtils::sDumpPaintFile, *ss);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::DumpPaintToFile()) {
+ *ss << "</body></html>";
+ }
+ if (gfxEnv::DumpPaintToFile()) {
+ fclose(gfxUtils::sDumpPaintFile);
+ }
+ gfxUtils::sDumpPaintFile = savedDumpFile;
+#endif
+
+ std::stringstream lsStream;
+ nsFrame::PrintDisplayList(&builder, list, lsStream);
+ layerManager->GetRoot()->SetDisplayListLog(lsStream.str().c_str());
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxPrefs::DumpClientLayers()) {
+ std::stringstream ss;
+ FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, false);
+ print_stderr(ss);
+ }
+#endif
+
+ // Update the widget's opaque region information. This sets
+ // glass boundaries on Windows. Also set up the window dragging region
+ // and plugin clip regions and bounds.
+ if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) &&
+ !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) {
+ nsIWidget *widget = aFrame->GetNearestWidget();
+ if (widget) {
+ nsRegion opaqueRegion;
+ opaqueRegion.And(builder.GetWindowExcludeGlassRegion(), builder.GetWindowOpaqueRegion());
+ widget->UpdateOpaqueRegion(
+ LayoutDeviceIntRegion::FromUnknownRegion(
+ opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
+
+ widget->UpdateWindowDraggingRegion(builder.GetWindowDraggingRegion());
+ }
+ }
+
+ if (builder.WillComputePluginGeometry()) {
+ // For single process compute and apply plugin geometry updates to plugin
+ // windows, then request composition. For content processes skip eveything
+ // except requesting composition. Geometry updates were calculated and
+ // shipped to the chrome process in nsDisplayList when the layer
+ // transaction completed.
+ if (XRE_IsParentProcess()) {
+ rootPresContext->ComputePluginGeometryUpdates(aFrame, &builder, &list);
+ // We're not going to get a WillPaintWindow event here if we didn't do
+ // widget invalidation, so just apply the plugin geometry update here
+ // instead. We could instead have the compositor send back an equivalent
+ // to WillPaintWindow, but it should be close enough to now not to matter.
+ if (layerManager && !layerManager->NeedsWidgetInvalidation()) {
+ rootPresContext->ApplyPluginGeometryUpdates();
+ }
+ }
+
+ // We told the compositor thread not to composite when it received the
+ // transaction because we wanted to update plugins first. Schedule the
+ // composite now.
+ if (layerManager) {
+ layerManager->Composite();
+ }
+ }
+
+
+ // Flush the list so we don't trigger the IsEmpty-on-destruction assertion
+ list.DeleteAll();
+ return NS_OK;
+}
+
+/**
+ * Uses a binary search for find where the cursor falls in the line of text
+ * It also keeps track of the part of the string that has already been measured
+ * so it doesn't have to keep measuring the same text over and over
+ *
+ * @param "aBaseWidth" contains the width in twips of the portion
+ * of the text that has already been measured, and aBaseInx contains
+ * the index of the text that has already been measured.
+ *
+ * @param aTextWidth returns the (in twips) the length of the text that falls
+ * before the cursor aIndex contains the index of the text where the cursor falls
+ */
+bool
+nsLayoutUtils::BinarySearchForPosition(DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ const char16_t* aText,
+ int32_t aBaseWidth,
+ int32_t aBaseInx,
+ int32_t aStartInx,
+ int32_t aEndInx,
+ int32_t aCursorPos,
+ int32_t& aIndex,
+ int32_t& aTextWidth)
+{
+ int32_t range = aEndInx - aStartInx;
+ if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) {
+ aIndex = aStartInx + aBaseInx;
+ aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex,
+ aFontMetrics, aDrawTarget);
+ return true;
+ }
+
+ int32_t inx = aStartInx + (range / 2);
+
+ // Make sure we don't leave a dangling low surrogate
+ if (NS_IS_HIGH_SURROGATE(aText[inx-1]))
+ inx++;
+
+ int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(aText, inx,
+ aFontMetrics,
+ aDrawTarget);
+
+ int32_t fullWidth = aBaseWidth + textWidth;
+ if (fullWidth == aCursorPos) {
+ aTextWidth = textWidth;
+ aIndex = inx;
+ return true;
+ } else if (aCursorPos < fullWidth) {
+ aTextWidth = aBaseWidth;
+ if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
+ aBaseInx, aStartInx, inx, aCursorPos, aIndex,
+ aTextWidth)) {
+ return true;
+ }
+ } else {
+ aTextWidth = fullWidth;
+ if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
+ aBaseInx, inx, aEndInx, aCursorPos, aIndex,
+ aTextWidth)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+AddBoxesForFrame(nsIFrame* aFrame,
+ nsLayoutUtils::BoxCallback* aCallback)
+{
+ nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo();
+
+ if (pseudoType == nsCSSAnonBoxes::tableWrapper) {
+ AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback);
+ if (aCallback->mIncludeCaptionBoxForTable) {
+ nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild();
+ if (kid) {
+ AddBoxesForFrame(kid, aCallback);
+ }
+ }
+ } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock ||
+ pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock ||
+ pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock ||
+ pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ AddBoxesForFrame(kid, aCallback);
+ }
+ } else {
+ aCallback->AddBox(aFrame);
+ }
+}
+
+void
+nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback)
+{
+ while (aFrame) {
+ AddBoxesForFrame(aFrame, aCallback);
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ }
+}
+
+nsIFrame*
+nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame)
+{
+ while (aFrame) {
+ nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo();
+
+ if (pseudoType == nsCSSAnonBoxes::tableWrapper) {
+ nsIFrame* f = GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild());
+ if (f) {
+ return f;
+ }
+ nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild();
+ if (kid) {
+ f = GetFirstNonAnonymousFrame(kid);
+ if (f) {
+ return f;
+ }
+ }
+ } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock ||
+ pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock ||
+ pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock ||
+ pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ nsIFrame* f = GetFirstNonAnonymousFrame(kid);
+ if (f) {
+ return f;
+ }
+ }
+ } else {
+ return aFrame;
+ }
+
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ }
+ return nullptr;
+}
+
+struct BoxToRect : public nsLayoutUtils::BoxCallback {
+ nsIFrame* mRelativeTo;
+ nsLayoutUtils::RectCallback* mCallback;
+ uint32_t mFlags;
+
+ BoxToRect(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback,
+ uint32_t aFlags)
+ : mRelativeTo(aRelativeTo), mCallback(aCallback), mFlags(aFlags) {}
+
+ virtual void AddBox(nsIFrame* aFrame) override {
+ nsRect r;
+ nsIFrame* outer = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
+ if (!outer) {
+ outer = aFrame;
+ switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) {
+ case nsLayoutUtils::RECTS_USE_CONTENT_BOX:
+ r = aFrame->GetContentRectRelativeToSelf();
+ break;
+ case nsLayoutUtils::RECTS_USE_PADDING_BOX:
+ r = aFrame->GetPaddingRectRelativeToSelf();
+ break;
+ case nsLayoutUtils::RECTS_USE_MARGIN_BOX:
+ r = aFrame->GetMarginRectRelativeToSelf();
+ break;
+ default: // Use the border box
+ r = aFrame->GetRectRelativeToSelf();
+ }
+ }
+ if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) {
+ r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
+ } else {
+ r += outer->GetOffsetTo(mRelativeTo);
+ }
+ mCallback->AddRect(r);
+ }
+};
+
+struct BoxToRectAndText : public BoxToRect {
+ mozilla::dom::DOMStringList* mTextList;
+
+ BoxToRectAndText(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback,
+ mozilla::dom::DOMStringList* aTextList, uint32_t aFlags)
+ : BoxToRect(aRelativeTo, aCallback, aFlags), mTextList(aTextList) {}
+
+ virtual void AddBox(nsIFrame* aFrame) override {
+ BoxToRect::AddBox(aFrame);
+ if (mTextList) {
+ nsIContent* content = aFrame->GetContent();
+ nsAutoString textContent;
+ mozilla::ErrorResult err; // ignored
+ content->GetTextContent(textContent, err);
+ mTextList->Add(textContent);
+ }
+ }
+};
+
+void
+nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ RectCallback* aCallback, uint32_t aFlags)
+{
+ BoxToRect converter(aRelativeTo, aCallback, aFlags);
+ GetAllInFlowBoxes(aFrame, &converter);
+}
+
+void
+nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ RectCallback* aCallback,
+ mozilla::dom::DOMStringList* aTextList,
+ uint32_t aFlags)
+{
+ BoxToRectAndText converter(aRelativeTo, aCallback, aTextList, aFlags);
+ GetAllInFlowBoxes(aFrame, &converter);
+}
+
+nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
+
+void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
+ mResultRect.UnionRect(mResultRect, aRect);
+ if (!mSeenFirstRect) {
+ mSeenFirstRect = true;
+ mFirstRect = aRect;
+ }
+}
+
+nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList)
+ : mRectList(aList)
+{
+}
+
+void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) {
+ RefPtr<DOMRect> rect = new DOMRect(mRectList);
+
+ rect->SetLayoutRect(aRect);
+ mRectList->Append(rect);
+}
+
+nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame)
+{
+ return aFrame->PresContext()->PresShell()->GetRootFrame();
+}
+
+nsRect
+nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ uint32_t aFlags) {
+ RectAccumulator accumulator;
+ GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
+ return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
+ : accumulator.mResultRect;
+}
+
+nsRect
+nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
+ nsIFrame* aFrame,
+ uint32_t aFlags)
+{
+ const nsStyleText* textStyle = aFrame->StyleText();
+ if (!textStyle->HasTextShadow())
+ return aTextAndDecorationsRect;
+
+ nsRect resultRect = aTextAndDecorationsRect;
+ int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
+ for (uint32_t i = 0; i < textStyle->mTextShadow->Length(); ++i) {
+ nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i);
+ nsMargin blur = nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D);
+ if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
+ continue;
+
+ nsRect tmpRect(aTextAndDecorationsRect);
+
+ tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
+ tmpRect.Inflate(blur);
+
+ resultRect.UnionRect(resultRect, tmpRect);
+ }
+ return resultRect;
+}
+
+enum ObjectDimensionType { eWidth, eHeight };
+static nscoord
+ComputeMissingDimension(const nsSize& aDefaultObjectSize,
+ const nsSize& aIntrinsicRatio,
+ const Maybe<nscoord>& aSpecifiedWidth,
+ const Maybe<nscoord>& aSpecifiedHeight,
+ ObjectDimensionType aDimensionToCompute)
+{
+ // The "default sizing algorithm" computes the missing dimension as follows:
+ // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
+
+ // 1. "If the object has an intrinsic aspect ratio, the missing dimension of
+ // the concrete object size is calculated using the intrinsic aspect
+ // ratio and the present dimension."
+ if (aIntrinsicRatio.width > 0 && aIntrinsicRatio.height > 0) {
+ // Fill in the missing dimension using the intrinsic aspect ratio.
+ nscoord knownDimensionSize;
+ float ratio;
+ if (aDimensionToCompute == eWidth) {
+ knownDimensionSize = *aSpecifiedHeight;
+ ratio = aIntrinsicRatio.width / aIntrinsicRatio.height;
+ } else {
+ knownDimensionSize = *aSpecifiedWidth;
+ ratio = aIntrinsicRatio.height / aIntrinsicRatio.width;
+ }
+ return NSCoordSaturatingNonnegativeMultiply(knownDimensionSize, ratio);
+ }
+
+ // 2. "Otherwise, if the missing dimension is present in the object’s
+ // intrinsic dimensions, [...]"
+ // NOTE: *Skipping* this case, because we already know it's not true -- we're
+ // in this function because the missing dimension is *not* present in
+ // the object's intrinsic dimensions.
+
+ // 3. "Otherwise, the missing dimension of the concrete object size is taken
+ // from the default object size. "
+ return (aDimensionToCompute == eWidth) ?
+ aDefaultObjectSize.width : aDefaultObjectSize.height;
+}
+
+/*
+ * This computes & returns the concrete object size of replaced content, if
+ * that content were to be rendered with "object-fit: none". (Or, if the
+ * element has neither an intrinsic height nor width, this method returns an
+ * empty Maybe<> object.)
+ *
+ * As specced...
+ * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none
+ * ..we use "the default sizing algorithm with no specified size,
+ * and a default object size equal to the replaced element's used width and
+ * height."
+ *
+ * The default sizing algorithm is described here:
+ * http://dev.w3.org/csswg/css-images-3/#default-sizing
+ * Quotes in the function-impl are taken from that ^ spec-text.
+ *
+ * Per its final bulleted section: since there's no specified size,
+ * we run the default sizing algorithm using the object's intrinsic size in
+ * place of the specified size. But if the object has neither an intrinsic
+ * height nor an intrinsic width, then we instead return without populating our
+ * outparam, and we let the caller figure out the size (using a contain
+ * constraint).
+ */
+static Maybe<nsSize>
+MaybeComputeObjectFitNoneSize(const nsSize& aDefaultObjectSize,
+ const IntrinsicSize& aIntrinsicSize,
+ const nsSize& aIntrinsicRatio)
+{
+ // "If the object has an intrinsic height or width, its size is resolved as
+ // if its intrinsic dimensions were given as the specified size."
+ //
+ // So, first we check if we have an intrinsic height and/or width:
+ Maybe<nscoord> specifiedWidth;
+ if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) {
+ specifiedWidth.emplace(aIntrinsicSize.width.GetCoordValue());
+ }
+
+ Maybe<nscoord> specifiedHeight;
+ if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
+ specifiedHeight.emplace(aIntrinsicSize.height.GetCoordValue());
+ }
+
+ Maybe<nsSize> noneSize; // (the value we'll return)
+ if (specifiedWidth || specifiedHeight) {
+ // We have at least one specified dimension; use whichever dimension is
+ // specified, and compute the other one using our intrinsic ratio, or (if
+ // no valid ratio) using the default object size.
+ noneSize.emplace();
+
+ noneSize->width = specifiedWidth ?
+ *specifiedWidth :
+ ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
+ specifiedWidth, specifiedHeight,
+ eWidth);
+
+ noneSize->height = specifiedHeight ?
+ *specifiedHeight :
+ ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
+ specifiedWidth, specifiedHeight,
+ eHeight);
+ }
+ // [else:] "Otherwise [if there's neither an intrinsic height nor width], its
+ // size is resolved as a contain constraint against the default object size."
+ // We'll let our caller do that, to share code & avoid redundant
+ // computations; so, we return w/out populating noneSize.
+ return noneSize;
+}
+
+// Computes the concrete object size to render into, as described at
+// http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
+static nsSize
+ComputeConcreteObjectSize(const nsSize& aConstraintSize,
+ const IntrinsicSize& aIntrinsicSize,
+ const nsSize& aIntrinsicRatio,
+ uint8_t aObjectFit)
+{
+ // Handle default behavior (filling the container) w/ fast early return.
+ // (Also: if there's no valid intrinsic ratio, then we have the "fill"
+ // behavior & just use the constraint size.)
+ if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) ||
+ aIntrinsicRatio.width == 0 ||
+ aIntrinsicRatio.height == 0) {
+ return aConstraintSize;
+ }
+
+ // The type of constraint to compute (cover/contain), if needed:
+ Maybe<nsImageRenderer::FitType> fitType;
+
+ Maybe<nsSize> noneSize;
+ if (aObjectFit == NS_STYLE_OBJECT_FIT_NONE ||
+ aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) {
+ noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize,
+ aIntrinsicRatio);
+ if (!noneSize || aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) {
+ // Need to compute a 'CONTAIN' constraint (either for the 'none' size
+ // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.)
+ fitType.emplace(nsImageRenderer::CONTAIN);
+ }
+ } else if (aObjectFit == NS_STYLE_OBJECT_FIT_COVER) {
+ fitType.emplace(nsImageRenderer::COVER);
+ } else if (aObjectFit == NS_STYLE_OBJECT_FIT_CONTAIN) {
+ fitType.emplace(nsImageRenderer::CONTAIN);
+ }
+
+ Maybe<nsSize> constrainedSize;
+ if (fitType) {
+ constrainedSize.emplace(
+ nsImageRenderer::ComputeConstrainedSize(aConstraintSize,
+ aIntrinsicRatio,
+ *fitType));
+ }
+
+ // Now, we should have all the sizing information that we need.
+ switch (aObjectFit) {
+ // skipping NS_STYLE_OBJECT_FIT_FILL; we handled it w/ early-return.
+ case NS_STYLE_OBJECT_FIT_CONTAIN:
+ case NS_STYLE_OBJECT_FIT_COVER:
+ MOZ_ASSERT(constrainedSize);
+ return *constrainedSize;
+
+ case NS_STYLE_OBJECT_FIT_NONE:
+ if (noneSize) {
+ return *noneSize;
+ }
+ MOZ_ASSERT(constrainedSize);
+ return *constrainedSize;
+
+ case NS_STYLE_OBJECT_FIT_SCALE_DOWN:
+ MOZ_ASSERT(constrainedSize);
+ if (noneSize) {
+ constrainedSize->width =
+ std::min(constrainedSize->width, noneSize->width);
+ constrainedSize->height =
+ std::min(constrainedSize->height, noneSize->height);
+ }
+ return *constrainedSize;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'");
+ return aConstraintSize; // fall back to (default) 'fill' behavior
+ }
+}
+
+// (Helper for HasInitialObjectFitAndPosition, to check
+// each "object-position" coord.)
+static bool
+IsCoord50Pct(const mozilla::Position::Coord& aCoord)
+{
+ return (aCoord.mLength == 0 &&
+ aCoord.mHasPercent &&
+ aCoord.mPercent == 0.5f);
+}
+
+// Indicates whether the given nsStylePosition has the initial values
+// for the "object-fit" and "object-position" properties.
+static bool
+HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos)
+{
+ const mozilla::Position& objectPos = aStylePos->mObjectPosition;
+
+ return aStylePos->mObjectFit == NS_STYLE_OBJECT_FIT_FILL &&
+ IsCoord50Pct(objectPos.mXPosition) &&
+ IsCoord50Pct(objectPos.mYPosition);
+}
+
+/* static */ nsRect
+nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
+ const IntrinsicSize& aIntrinsicSize,
+ const nsSize& aIntrinsicRatio,
+ const nsStylePosition* aStylePos,
+ nsPoint* aAnchorPoint)
+{
+ // Step 1: Figure out our "concrete object size"
+ // (the size of the region we'll actually draw our image's pixels into).
+ nsSize concreteObjectSize =
+ ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
+ aIntrinsicRatio, aStylePos->mObjectFit);
+
+ // Step 2: Figure out how to align that region in the element's content-box.
+ nsPoint imageTopLeftPt, imageAnchorPt;
+ nsImageRenderer::ComputeObjectAnchorPoint(aStylePos->mObjectPosition,
+ aConstraintRect.Size(),
+ concreteObjectSize,
+ &imageTopLeftPt, &imageAnchorPt);
+ // Right now, we're with respect to aConstraintRect's top-left point. We add
+ // that point here, to convert to the same broader coordinate space that
+ // aConstraintRect is in.
+ imageTopLeftPt += aConstraintRect.TopLeft();
+ imageAnchorPt += aConstraintRect.TopLeft();
+
+ if (aAnchorPoint) {
+ // Special-case: if our "object-fit" and "object-position" properties have
+ // their default values ("object-fit: fill; object-position:50% 50%"), then
+ // we'll override the calculated imageAnchorPt, and instead use the
+ // object's top-left corner.
+ //
+ // This special case is partly for backwards compatibility (since
+ // traditionally we've pixel-aligned the top-left corner of e.g. <img>
+ // elements), and partly because ComputeSnappedDrawingParameters produces
+ // less error if the anchor point is at the top-left corner. So, all other
+ // things being equal, we prefer that code path with less error.
+ if (HasInitialObjectFitAndPosition(aStylePos)) {
+ *aAnchorPoint = imageTopLeftPt;
+ } else {
+ *aAnchorPoint = imageAnchorPt;
+ }
+ }
+ return nsRect(imageTopLeftPt, concreteObjectSize);
+}
+
+already_AddRefed<nsFontMetrics>
+nsLayoutUtils::GetFontMetricsForFrame(const nsIFrame* aFrame, float aInflation)
+{
+ nsStyleContext* styleContext = aFrame->StyleContext();
+ uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL;
+ if (styleContext->IsTextCombined()) {
+ MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::textFrame);
+ auto textFrame = static_cast<const nsTextFrame*>(aFrame);
+ auto clusters = textFrame->CountGraphemeClusters();
+ if (clusters == 2) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_HALF;
+ } else if (clusters == 3) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_THIRD;
+ } else if (clusters == 4) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER;
+ }
+ }
+ return GetFontMetricsForStyleContext(styleContext, aInflation, variantWidth);
+}
+
+already_AddRefed<nsFontMetrics>
+nsLayoutUtils::GetFontMetricsForStyleContext(nsStyleContext* aStyleContext,
+ float aInflation,
+ uint8_t aVariantWidth)
+{
+ nsPresContext* pc = aStyleContext->PresContext();
+
+ WritingMode wm(aStyleContext);
+ const nsStyleFont* styleFont = aStyleContext->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.orientation =
+ wm.IsVertical() && !wm.IsSideways() ? gfxFont::eVertical
+ : gfxFont::eHorizontal;
+ // pass the user font set object into the device context to
+ // pass along to CreateFontGroup
+ params.userFontSet = pc->GetUserFontSet();
+ params.textPerf = pc->GetTextPerfMetrics();
+
+ // When aInflation is 1.0 and we don't require width variant, avoid
+ // making a local copy of the nsFont.
+ // This also avoids running font.size through floats when it is large,
+ // which would be lossy. Fortunately, in such cases, aInflation is
+ // guaranteed to be 1.0f.
+ if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) {
+ return pc->DeviceContext()->GetMetricsFor(styleFont->mFont, params);
+ }
+
+ nsFont font = styleFont->mFont;
+ font.size = NSToCoordRound(font.size * aInflation);
+ font.variantWidth = aVariantWidth;
+ return pc->DeviceContext()->GetMetricsFor(font, params);
+}
+
+nsIFrame*
+nsLayoutUtils::FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame)
+{
+ nsIFrame* result = aDescendantFrame;
+
+ while (result) {
+ nsIFrame* parent = result->GetParent();
+ if (parent == aParent) {
+ break;
+ }
+
+ // The frame is not an immediate child of aParent so walk up another level
+ result = parent;
+ }
+
+ return result;
+}
+
+nsBlockFrame*
+nsLayoutUtils::GetAsBlock(nsIFrame* aFrame)
+{
+ nsBlockFrame* block = do_QueryFrame(aFrame);
+ return block;
+}
+
+nsBlockFrame*
+nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame)
+{
+ nsIFrame* nextAncestor;
+ for (nextAncestor = aFrame->GetParent(); nextAncestor;
+ nextAncestor = nextAncestor->GetParent()) {
+ nsBlockFrame* block = GetAsBlock(nextAncestor);
+ if (block)
+ return block;
+ }
+ return nullptr;
+}
+
+nsIFrame*
+nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame)
+{
+ if (!(aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT))
+ return aFrame;
+
+ nsIFrame* f = aFrame;
+ do {
+ f = GetParentOrPlaceholderFor(f);
+ } while (f->GetStateBits() & NS_FRAME_GENERATED_CONTENT);
+ return f;
+}
+
+nsIFrame*
+nsLayoutUtils::GetParentOrPlaceholderFor(nsIFrame* aFrame)
+{
+ if ((aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
+ && !aFrame->GetPrevInFlow()) {
+ return aFrame->PresContext()->PresShell()->FrameManager()->
+ GetPlaceholderFrameFor(aFrame);
+ }
+ return aFrame->GetParent();
+}
+
+nsIFrame*
+nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(nsIFrame* aFrame)
+{
+ nsIFrame* f = GetParentOrPlaceholderFor(aFrame);
+ if (f)
+ return f;
+ return GetCrossDocParentFrame(aFrame);
+}
+
+nsIFrame*
+nsLayoutUtils::GetNextContinuationOrIBSplitSibling(nsIFrame *aFrame)
+{
+ nsIFrame *result = aFrame->GetNextContinuation();
+ if (result)
+ return result;
+
+ if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) != 0) {
+ // We only store the ib-split sibling annotation with the first
+ // frame in the continuation chain. Walk back to find that frame now.
+ aFrame = aFrame->FirstContinuation();
+
+ return aFrame->Properties().Get(nsIFrame::IBSplitSibling());
+ }
+
+ return nullptr;
+}
+
+nsIFrame*
+nsLayoutUtils::FirstContinuationOrIBSplitSibling(nsIFrame *aFrame)
+{
+ nsIFrame *result = aFrame->FirstContinuation();
+ if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) {
+ while (true) {
+ nsIFrame* f =
+ result->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ if (!f)
+ break;
+ result = f;
+ }
+ }
+
+ return result;
+}
+
+nsIFrame*
+nsLayoutUtils::LastContinuationOrIBSplitSibling(nsIFrame *aFrame)
+{
+ nsIFrame *result = aFrame->FirstContinuation();
+ if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) {
+ while (true) {
+ nsIFrame* f =
+ result->Properties().Get(nsIFrame::IBSplitSibling());
+ if (!f)
+ break;
+ result = f;
+ }
+ }
+
+ result = result->LastContinuation();
+
+ return result;
+}
+
+bool
+nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(nsIFrame *aFrame)
+{
+ if (aFrame->GetPrevContinuation()) {
+ return false;
+ }
+ if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) &&
+ aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return false;
+
+ nsIFrame* rootScrollFrame =
+ aFrame->PresContext()->PresShell()->GetRootScrollFrame();
+ if (!rootScrollFrame)
+ return false;
+
+ nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
+ NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null");
+
+ if (!IsProperAncestorFrame(rootScrollFrame, aFrame))
+ return false;
+
+ nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame();
+ return !(rootScrolledFrame == aFrame ||
+ IsProperAncestorFrame(rootScrolledFrame, aFrame));
+}
+
+// Use only for widths/heights (or their min/max), since it clamps
+// negative calc() results to 0.
+static bool GetAbsoluteCoord(const nsStyleCoord& aStyle, nscoord& aResult)
+{
+ if (aStyle.IsCalcUnit()) {
+ if (aStyle.CalcHasPercent()) {
+ return false;
+ }
+ // If it has no percents, we can pass 0 for the percentage basis.
+ aResult = nsRuleNode::ComputeComputedCalc(aStyle, 0);
+ if (aResult < 0)
+ aResult = 0;
+ return true;
+ }
+
+ if (eStyleUnit_Coord != aStyle.GetUnit())
+ return false;
+
+ aResult = aStyle.GetCoordValue();
+ NS_ASSERTION(aResult >= 0, "negative widths not allowed");
+ return true;
+}
+
+static nscoord
+GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing,
+ nsIFrame* aFrame,
+ bool aHorizontalAxis,
+ bool aIgnorePadding);
+
+// Only call on style coords for which GetAbsoluteCoord returned false.
+static bool
+GetPercentBSize(const nsStyleCoord& aStyle,
+ nsIFrame* aFrame,
+ bool aHorizontalAxis,
+ nscoord& aResult)
+{
+ if (eStyleUnit_Percent != aStyle.GetUnit() &&
+ !aStyle.IsCalcUnit())
+ return false;
+
+ MOZ_ASSERT(!aStyle.IsCalcUnit() || aStyle.CalcHasPercent(),
+ "GetAbsoluteCoord should have handled this");
+
+ // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses
+ // SetComputedHeight on the reflow state for its child to propagate its
+ // computed height to the scrolled content. So here we skip to the scroll
+ // frame that contains this scrolled content in order to get the same
+ // behavior as layout when computing percentage heights.
+ nsIFrame *f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
+ if (!f) {
+ NS_NOTREACHED("top of frame tree not a containing block");
+ return false;
+ }
+
+ WritingMode wm = f->GetWritingMode();
+
+ const nsStylePosition *pos = f->StylePosition();
+ const nsStyleCoord& bSizeCoord = pos->BSize(wm);
+ nscoord h;
+ if (!GetAbsoluteCoord(bSizeCoord, h) &&
+ !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
+ NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto ||
+ bSizeCoord.HasPercent(),
+ "unknown block-size unit");
+ nsIAtom* fType = f->GetType();
+ if (fType != nsGkAtoms::viewportFrame && fType != nsGkAtoms::canvasFrame &&
+ fType != nsGkAtoms::pageContentFrame) {
+ // There's no basis for the percentage height, so it acts like auto.
+ // Should we consider a max-height < min-height pair a basis for
+ // percentage heights? The spec is somewhat unclear, and not doing
+ // so is simpler and avoids troubling discontinuities in behavior,
+ // so I'll choose not to. -LDB
+ return false;
+ }
+
+ NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto,
+ "Unexpected block-size unit for viewport or canvas or page-content");
+ // For the viewport, canvas, and page-content kids, the percentage
+ // basis is just the parent block-size.
+ h = f->BSize(wm);
+ if (h == NS_UNCONSTRAINEDSIZE) {
+ // We don't have a percentage basis after all
+ return false;
+ }
+ }
+
+ const nsStyleCoord& maxBSizeCoord = pos->MaxBSize(wm);
+
+ nscoord maxh;
+ if (GetAbsoluteCoord(maxBSizeCoord, maxh) ||
+ GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) {
+ if (maxh < h)
+ h = maxh;
+ } else {
+ NS_ASSERTION(maxBSizeCoord.GetUnit() == eStyleUnit_None ||
+ maxBSizeCoord.HasPercent(),
+ "unknown max block-size unit");
+ }
+
+ const nsStyleCoord& minBSizeCoord = pos->MinBSize(wm);
+
+ nscoord minh;
+ if (GetAbsoluteCoord(minBSizeCoord, minh) ||
+ GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) {
+ if (minh > h)
+ h = minh;
+ } else {
+ NS_ASSERTION(minBSizeCoord.HasPercent() ||
+ minBSizeCoord.GetUnit() == eStyleUnit_Auto,
+ "unknown min block-size unit");
+ }
+
+ // Now adjust h for box-sizing styles on the parent. We never ignore padding
+ // here. That could conceivably cause some problems with fieldsets (which are
+ // the one place that wants to ignore padding), but solving that here without
+ // hardcoding a check for f being a fieldset-content frame is a bit of a pain.
+ nscoord bSizeTakenByBoxSizing =
+ GetBSizeTakenByBoxSizing(pos->mBoxSizing, f, aHorizontalAxis, false);
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+
+ if (aStyle.IsCalcUnit()) {
+ aResult = std::max(nsRuleNode::ComputeComputedCalc(aStyle, h), 0);
+ return true;
+ }
+
+ aResult = NSToCoordRound(aStyle.GetPercentValue() * h);
+ return true;
+}
+
+// Get the amount of vertical space taken out of aFrame's content area due to
+// its borders and paddings given the box-sizing value in aBoxSizing. We don't
+// get aBoxSizing from the frame because some callers want to compute this for
+// specific box-sizing values. aHorizontalAxis is true if our inline direction
+// is horisontal and our block direction is vertical. aIgnorePadding is true if
+// padding should be ignored.
+static nscoord
+GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing,
+ nsIFrame* aFrame,
+ bool aHorizontalAxis,
+ bool aIgnorePadding)
+{
+ nscoord bSizeTakenByBoxSizing = 0;
+ if (aBoxSizing == StyleBoxSizing::Border) {
+ const nsStyleBorder* styleBorder = aFrame->StyleBorder();
+ bSizeTakenByBoxSizing +=
+ aHorizontalAxis ? styleBorder->GetComputedBorder().TopBottom()
+ : styleBorder->GetComputedBorder().LeftRight();
+ if (!aIgnorePadding) {
+ const nsStyleSides& stylePadding =
+ aFrame->StylePadding()->mPadding;
+ const nsStyleCoord& paddingStart =
+ stylePadding.Get(aHorizontalAxis ? NS_SIDE_TOP : NS_SIDE_LEFT);
+ const nsStyleCoord& paddingEnd =
+ stylePadding.Get(aHorizontalAxis ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT);
+ nscoord pad;
+ // XXXbz Calling GetPercentBSize on padding values looks bogus, since
+ // percent padding is always a percentage of the inline-size of the
+ // containing block. We should perhaps just treat non-absolute paddings
+ // here as 0 instead, except that in some cases the width may in fact be
+ // known. See bug 1231059.
+ if (GetAbsoluteCoord(paddingStart, pad) ||
+ GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) {
+ bSizeTakenByBoxSizing += pad;
+ }
+ if (GetAbsoluteCoord(paddingEnd, pad) ||
+ GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) {
+ bSizeTakenByBoxSizing += pad;
+ }
+ }
+ }
+ return bSizeTakenByBoxSizing;
+}
+
+// Handles only -moz-max-content and -moz-min-content, and
+// -moz-fit-content for min-width and max-width, since the others
+// (-moz-fit-content for width, and -moz-available) have no effect on
+// intrinsic widths.
+enum eWidthProperty { PROP_WIDTH, PROP_MAX_WIDTH, PROP_MIN_WIDTH };
+static bool
+GetIntrinsicCoord(const nsStyleCoord& aStyle,
+ nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ eWidthProperty aProperty,
+ nscoord& aResult)
+{
+ NS_PRECONDITION(aProperty == PROP_WIDTH || aProperty == PROP_MAX_WIDTH ||
+ aProperty == PROP_MIN_WIDTH, "unexpected property");
+ if (aStyle.GetUnit() != eStyleUnit_Enumerated)
+ return false;
+ int32_t val = aStyle.GetIntValue();
+ NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT ||
+ val == NS_STYLE_WIDTH_MIN_CONTENT ||
+ val == NS_STYLE_WIDTH_FIT_CONTENT ||
+ val == NS_STYLE_WIDTH_AVAILABLE,
+ "unexpected enumerated value for width property");
+ if (val == NS_STYLE_WIDTH_AVAILABLE)
+ return false;
+ if (val == NS_STYLE_WIDTH_FIT_CONTENT) {
+ if (aProperty == PROP_WIDTH)
+ return false; // handle like 'width: auto'
+ if (aProperty == PROP_MAX_WIDTH)
+ // constrain large 'width' values down to -moz-max-content
+ val = NS_STYLE_WIDTH_MAX_CONTENT;
+ else
+ // constrain small 'width' or 'max-width' values up to -moz-min-content
+ val = NS_STYLE_WIDTH_MIN_CONTENT;
+ }
+
+ NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT ||
+ val == NS_STYLE_WIDTH_MIN_CONTENT,
+ "should have reduced everything remaining to one of these");
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ if (val == NS_STYLE_WIDTH_MAX_CONTENT)
+ aResult = aFrame->GetPrefISize(aRenderingContext);
+ else
+ aResult = aFrame->GetMinISize(aRenderingContext);
+ return true;
+}
+
+#undef DEBUG_INTRINSIC_WIDTH
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+static int32_t gNoiseIndent = 0;
+#endif
+
+// Return true for form controls whose minimum intrinsic inline-size
+// shrinks to 0 when they have a percentage inline-size (but not
+// percentage max-inline-size). (Proper replaced elements, whose
+// intrinsic minimium inline-size shrinks to 0 for both percentage
+// inline-size and percentage max-inline-size, are handled elsewhere.)
+inline static bool
+FormControlShrinksForPercentISize(nsIFrame* aFrame)
+{
+ if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
+ // Quick test to reject most frames.
+ return false;
+ }
+
+ nsIAtom* fType = aFrame->GetType();
+ if (fType == nsGkAtoms::meterFrame || fType == nsGkAtoms::progressFrame) {
+ // progress and meter do have this shrinking behavior
+ // FIXME: Maybe these should be nsIFormControlFrame?
+ return true;
+ }
+
+ if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
+ // Not a form control. This includes fieldsets, which do not
+ // shrink.
+ return false;
+ }
+
+ if (fType == nsGkAtoms::gfxButtonControlFrame ||
+ fType == nsGkAtoms::HTMLButtonControlFrame) {
+ // Buttons don't have this shrinking behavior. (Note that color
+ // inputs do, even though they inherit from button, so we can't use
+ // do_QueryFrame here.)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Add aOffsets which describes what to add on outside of the content box
+ * aContentSize (controlled by 'box-sizing') and apply min/max properties.
+ * We have to account for these properties after getting all the offsets
+ * (margin, border, padding) because percentages do not operate linearly.
+ * Doing this is ok because although percentages aren't handled linearly,
+ * they are handled monotonically.
+ *
+ * @param aContentSize the content size calculated so far
+ (@see IntrinsicForContainer)
+ * @param aContentMinSize ditto min content size
+ * @param aStyleSize a 'width' or 'height' property value
+ * @param aFixedMinSize if aStyleMinSize is a definite size then this points to
+ * the value, otherwise nullptr
+ * @param aStyleMinSize a 'min-width' or 'min-height' property value
+ * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to
+ * the value, otherwise nullptr
+ * @param aStyleMaxSize a 'max-width' or 'max-height' property value
+ * @param aFlags same as for IntrinsicForContainer
+ * @param aContainerWM the container's WM
+ */
+static nscoord
+AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ const nsIFrame::IntrinsicISizeOffsetData& aOffsets,
+ nsLayoutUtils::IntrinsicISizeType aType,
+ StyleBoxSizing aBoxSizing,
+ nscoord aContentSize,
+ nscoord aContentMinSize,
+ const nsStyleCoord& aStyleSize,
+ const nscoord* aFixedMinSize,
+ const nsStyleCoord& aStyleMinSize,
+ const nscoord* aFixedMaxSize,
+ const nsStyleCoord& aStyleMaxSize,
+ uint32_t aFlags,
+ PhysicalAxis aAxis)
+{
+ nscoord result = aContentSize;
+ nscoord min = aContentMinSize;
+ nscoord coordOutsideSize = 0;
+ float pctOutsideSize = 0;
+ float pctTotal = 0.0f;
+
+ if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) {
+ coordOutsideSize += aOffsets.hPadding;
+ pctOutsideSize += aOffsets.hPctPadding;
+ }
+
+ coordOutsideSize += aOffsets.hBorder;
+
+ if (aBoxSizing == StyleBoxSizing::Border) {
+ min += coordOutsideSize;
+ result = NSCoordSaturatingAdd(result, coordOutsideSize);
+ pctTotal += pctOutsideSize;
+
+ coordOutsideSize = 0;
+ pctOutsideSize = 0.0f;
+ }
+
+ coordOutsideSize += aOffsets.hMargin;
+ pctOutsideSize += aOffsets.hPctMargin;
+
+ min += coordOutsideSize;
+ result = NSCoordSaturatingAdd(result, coordOutsideSize);
+ pctTotal += pctOutsideSize;
+
+ const bool shouldAddPercent = aType == nsLayoutUtils::PREF_ISIZE ||
+ (aFlags & nsLayoutUtils::ADD_PERCENTS);
+ nscoord size;
+ if (aType == nsLayoutUtils::MIN_ISIZE &&
+ (((aStyleSize.HasPercent() || aStyleMaxSize.HasPercent()) &&
+ aFrame->IsFrameOfType(nsIFrame::eReplacedSizing)) ||
+ (aStyleSize.HasPercent() &&
+ FormControlShrinksForPercentISize(aFrame)))) {
+ // A percentage width or max-width on replaced elements means they
+ // can shrink to 0.
+ // This is also true for percentage widths (but not max-widths) on
+ // text inputs.
+ // Note that if this is max-width, this overrides the fixed-width
+ // rule in the next condition.
+ result = 0; // let |min| handle padding/border/margin
+ } else if (GetAbsoluteCoord(aStyleSize, size) ||
+ GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
+ PROP_WIDTH, size)) {
+ result = size + coordOutsideSize;
+ if (shouldAddPercent) {
+ result = nsLayoutUtils::AddPercents(result, pctOutsideSize);
+ }
+ } else {
+ // NOTE: We could really do a lot better for percents and for some
+ // cases of calc() containing percent (certainly including any where
+ // the coefficient on the percent is positive and there are no max()
+ // expressions). However, doing better for percents wouldn't be
+ // backwards compatible.
+ if (shouldAddPercent) {
+ result = nsLayoutUtils::AddPercents(result, pctTotal);
+ }
+ }
+
+ nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0;
+ if (aFixedMaxSize ||
+ GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame,
+ PROP_MAX_WIDTH, maxSize)) {
+ maxSize += coordOutsideSize;
+ if (shouldAddPercent) {
+ maxSize = nsLayoutUtils::AddPercents(maxSize, pctOutsideSize);
+ }
+ if (result > maxSize) {
+ result = maxSize;
+ }
+ }
+
+ nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0;
+ if (aFixedMinSize ||
+ GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame,
+ PROP_MIN_WIDTH, minSize)) {
+ minSize += coordOutsideSize;
+ if (shouldAddPercent) {
+ minSize = nsLayoutUtils::AddPercents(minSize, pctOutsideSize);
+ }
+ if (result < minSize) {
+ result = minSize;
+ }
+ }
+
+ if (shouldAddPercent) {
+ min = nsLayoutUtils::AddPercents(min, pctTotal);
+ }
+ if (result < min) {
+ result = min;
+ }
+
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ if (aFrame->IsThemed(disp)) {
+ LayoutDeviceIntSize devSize;
+ bool canOverride = true;
+ nsPresContext* pc = aFrame->PresContext();
+ pc->GetTheme()->GetMinimumWidgetSize(pc, aFrame, disp->mAppearance,
+ &devSize, &canOverride);
+ nscoord themeSize =
+ pc->DevPixelsToAppUnits(aAxis == eAxisVertical ? devSize.height
+ : devSize.width);
+ // GetMinimumWidgetSize() returns a border-box width.
+ themeSize += aOffsets.hMargin;
+ if (shouldAddPercent) {
+ themeSize = nsLayoutUtils::AddPercents(themeSize, aOffsets.hPctMargin);
+ }
+ if (themeSize > result || !canOverride) {
+ result = themeSize;
+ }
+ }
+ return result;
+}
+
+static void
+AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit)
+{
+ for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ if (f->HasAnyStateBits(aBit)) {
+ break;
+ }
+ f->AddStateBits(aBit);
+ }
+}
+
+/* static */ nscoord
+nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis,
+ nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags,
+ nscoord aMarginBoxMinSizeClamp)
+{
+ NS_PRECONDITION(aFrame, "null frame");
+ NS_PRECONDITION(aFrame->GetParent(),
+ "IntrinsicForAxis called on frame not in tree");
+ NS_PRECONDITION(aType == MIN_ISIZE || aType == PREF_ISIZE, "bad type");
+
+ const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal);
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size for container:\n",
+ aType == MIN_ISIZE ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical");
+#endif
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ // We want the size this frame will contribute to the parent's inline-size,
+ // so we work in the parent's writing mode; but if aFrame is orthogonal to
+ // its parent, we'll need to look at its BSize instead of min/pref-ISize.
+ const nsStylePosition* stylePos = aFrame->StylePosition();
+ StyleBoxSizing boxSizing = stylePos->mBoxSizing;
+
+ const nsStyleCoord& styleMinISize =
+ horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight;
+ const nsStyleCoord& styleISize =
+ (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize :
+ (horizontalAxis ? stylePos->mWidth : stylePos->mHeight);
+ MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) ||
+ styleISize.GetUnit() == eStyleUnit_Auto ||
+ styleISize.GetUnit() == eStyleUnit_Enumerated,
+ "should only use MIN_INTRINSIC_ISIZE for intrinsic values");
+ const nsStyleCoord& styleMaxISize =
+ horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+
+ // We build up two values starting with the content box, and then
+ // adding padding, border and margin. The result is normally
+ // |result|. Then, when we handle 'width', 'min-width', and
+ // 'max-width', we use the results we've been building in |min| as a
+ // minimum, overriding 'min-width'. This ensures two things:
+ // * that we don't let a value of 'box-sizing' specifying a width
+ // smaller than the padding/border inside the box-sizing box give
+ // a content width less than zero
+ // * that we prevent tables from becoming smaller than their
+ // intrinsic minimum width
+ nscoord result = 0, min = 0;
+
+ nscoord maxISize;
+ bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize);
+ nscoord minISize;
+
+ // Treat "min-width: auto" as 0.
+ bool haveFixedMinISize;
+ if (eStyleUnit_Auto == styleMinISize.GetUnit()) {
+ // NOTE: Technically, "auto" is supposed to behave like "min-content" on
+ // flex items. However, we don't need to worry about that here, because
+ // flex items' min-sizes are intentionally ignored until the flex
+ // container explicitly considers them during space distribution.
+ minISize = 0;
+ haveFixedMinISize = true;
+ } else {
+ haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize);
+ }
+
+ PhysicalAxis ourInlineAxis =
+ aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ // If we have a specified width (or a specified 'min-width' greater
+ // than the specified 'max-width', which works out to the same thing),
+ // don't even bother getting the frame's intrinsic width, because in
+ // this case GetAbsoluteCoord(styleISize, w) will always succeed, so
+ // we'll never need the intrinsic dimensions.
+ if (styleISize.GetUnit() == eStyleUnit_Enumerated &&
+ (styleISize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
+ styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT)) {
+ // -moz-fit-content and -moz-available enumerated widths compute intrinsic
+ // widths just like auto.
+ // For -moz-max-content and -moz-min-content, we handle them like
+ // specified widths, but ignore box-sizing.
+ boxSizing = StyleBoxSizing::Content;
+ if (aMarginBoxMinSizeClamp != NS_MAXSIZE &&
+ styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT) {
+ // We need |result| to be the 'min-content size' for the clamping below.
+ result = aFrame->GetMinISize(aRenderingContext);
+ }
+ } else if (!styleISize.ConvertsToLength() &&
+ !(haveFixedMinISize && haveFixedMaxISize && maxISize <= minISize)) {
+#ifdef DEBUG_INTRINSIC_WIDTH
+ ++gNoiseIndent;
+#endif
+ if (aType != MIN_ISIZE) {
+ // At this point, |styleISize| is auto/-moz-fit-content/-moz-available or
+ // has a percentage. The intrinisic size for those under a max-content
+ // constraint is the max-content contribution which we shouldn't clamp.
+ aMarginBoxMinSizeClamp = NS_MAXSIZE;
+ }
+ if (MOZ_UNLIKELY(aAxis != ourInlineAxis)) {
+ IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize();
+ const nsStyleCoord intrinsicBCoord =
+ horizontalAxis ? intrinsicSize.width : intrinsicSize.height;
+ if (intrinsicBCoord.GetUnit() == eStyleUnit_Coord) {
+ result = intrinsicBCoord.GetCoordValue();
+ } else {
+ // We don't have an intrinsic bsize and we need aFrame's block-dir size.
+ if (aFlags & BAIL_IF_REFLOW_NEEDED) {
+ return NS_INTRINSIC_WIDTH_UNKNOWN;
+ }
+ // XXX Unfortunately, we probably don't know this yet, so this is wrong...
+ // but it's not clear what we should do. If aFrame's inline size hasn't
+ // been determined yet, we can't necessarily figure out its block size
+ // either. For now, authors who put orthogonal elements into things like
+ // buttons or table cells may have to explicitly provide sizes rather
+ // than expecting intrinsic sizing to work "perfectly" in underspecified
+ // cases.
+ result = aFrame->BSize();
+ }
+ } else {
+ result = aType == MIN_ISIZE
+ ? aFrame->GetMinISize(aRenderingContext)
+ : aFrame->GetPrefISize(aRenderingContext);
+ }
+#ifdef DEBUG_INTRINSIC_WIDTH
+ --gNoiseIndent;
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size from frame is %d.\n",
+ aType == MIN_ISIZE ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical",
+ result);
+#endif
+
+ // Handle elements with an intrinsic ratio (or size) and a specified
+ // height, min-height, or max-height.
+ // NOTE: We treat "min-height:auto" as "0" for the purpose of this code,
+ // since that's what it means in all cases except for on flex items -- and
+ // even there, we're supposed to ignore it (i.e. treat it as 0) until the
+ // flex container explicitly considers it.
+ const nsStyleCoord& styleBSize =
+ horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
+ const nsStyleCoord& styleMinBSize =
+ horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth;
+ const nsStyleCoord& styleMaxBSize =
+ horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth;
+
+ if (styleBSize.GetUnit() != eStyleUnit_Auto ||
+ !(styleMinBSize.GetUnit() == eStyleUnit_Auto ||
+ (styleMinBSize.GetUnit() == eStyleUnit_Coord &&
+ styleMinBSize.GetCoordValue() == 0)) ||
+ styleMaxBSize.GetUnit() != eStyleUnit_None) {
+
+ nsSize ratio(aFrame->GetIntrinsicRatio());
+ nscoord ratioISize = (horizontalAxis ? ratio.width : ratio.height);
+ nscoord ratioBSize = (horizontalAxis ? ratio.height : ratio.width);
+ if (ratioBSize != 0) {
+ AddStateBitToAncestors(aFrame,
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
+
+ nscoord bSizeTakenByBoxSizing =
+ GetBSizeTakenByBoxSizing(boxSizing, aFrame, horizontalAxis,
+ aFlags & IGNORE_PADDING);
+
+ // NOTE: This is only the minContentSize if we've been passed MIN_INTRINSIC_ISIZE
+ // (which is fine, because this should only be used inside a check for that flag).
+ nscoord minContentSize = result;
+ nscoord h;
+ if (GetAbsoluteCoord(styleBSize, h) ||
+ GetPercentBSize(styleBSize, aFrame, horizontalAxis, h)) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ result = NSCoordMulDiv(h, ratioISize, ratioBSize);
+ }
+
+ if (GetAbsoluteCoord(styleMaxBSize, h) ||
+ GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h)) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ nscoord maxISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
+ if (maxISize < result) {
+ result = maxISize;
+ }
+ if (maxISize < minContentSize) {
+ minContentSize = maxISize;
+ }
+ }
+
+ if (GetAbsoluteCoord(styleMinBSize, h) ||
+ GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h)) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ nscoord minISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
+ if (minISize > result) {
+ result = minISize;
+ }
+ if (minISize > minContentSize) {
+ minContentSize = minISize;
+ }
+ }
+ if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE)) {
+ // This is the 'min-width/height:auto' "transferred size" piece of:
+ // https://www.w3.org/TR/css-flexbox-1/#min-width-automatic-minimum-size
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ result = std::min(result, minContentSize);
+ }
+ }
+ }
+ }
+
+ if (aFrame->GetType() == nsGkAtoms::tableFrame) {
+ // Tables can't shrink smaller than their intrinsic minimum width,
+ // no matter what.
+ min = aFrame->GetMinISize(aRenderingContext);
+ }
+
+ nsIFrame::IntrinsicISizeOffsetData offsets =
+ MOZ_LIKELY(aAxis == ourInlineAxis) ? aFrame->IntrinsicISizeOffsets()
+ : aFrame->IntrinsicBSizeOffsets();
+ nscoord contentBoxSize = result;
+ result = AddIntrinsicSizeOffset(aRenderingContext, aFrame, offsets, aType,
+ boxSizing, result, min, styleISize,
+ haveFixedMinISize ? &minISize : nullptr,
+ styleMinISize,
+ haveFixedMaxISize ? &maxISize : nullptr,
+ styleMaxISize,
+ aFlags, aAxis);
+ nscoord overflow = result - aMarginBoxMinSizeClamp;
+ if (MOZ_UNLIKELY(overflow > 0)) {
+ nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow);
+ result -= contentBoxSize - newContentBoxSize;
+ }
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size for container is %d twips.\n",
+ aType == MIN_ISIZE ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical",
+ result);
+#endif
+
+ return result;
+}
+
+/* static */ nscoord
+nsLayoutUtils::IntrinsicForContainer(nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(aFrame && aFrame->GetParent());
+ // We want the size aFrame will contribute to its parent's inline-size.
+ PhysicalAxis axis =
+ aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, aFlags);
+}
+
+/* static */ nscoord
+nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis,
+ nsRenderingContext* aRC,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->IsFlexOrGridItem(),
+ "only grid/flex items have this behavior currently");
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s min-isize for %s WM:\n",
+ aType == MIN_ISIZE ? "min" : "pref",
+ aWM.IsVertical() ? "vertical" : "horizontal");
+#endif
+
+ // Note: this method is only meant for grid/flex items which always
+ // include percentages in their intrinsic size.
+ aFlags |= nsLayoutUtils::ADD_PERCENTS;
+ const nsStylePosition* const stylePos = aFrame->StylePosition();
+ const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth
+ : &stylePos->mMinHeight;
+ nscoord minSize;
+ nscoord* fixedMinSize = nullptr;
+ auto minSizeUnit = style->GetUnit();
+ if (minSizeUnit == eStyleUnit_Auto) {
+ if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) {
+ style = aAxis == eAxisHorizontal ? &stylePos->mWidth
+ : &stylePos->mHeight;
+ if (GetAbsoluteCoord(*style, minSize)) {
+ // We have a definite width/height. This is the "specified size" in:
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ fixedMinSize = &minSize;
+ }
+ // fall through - the caller will have to deal with "transferred size"
+ } else {
+ // min-[width|height]:auto with overflow != visible computes to zero.
+ minSize = 0;
+ fixedMinSize = &minSize;
+ }
+ } else if (GetAbsoluteCoord(*style, minSize)) {
+ fixedMinSize = &minSize;
+ } else if (minSizeUnit != eStyleUnit_Enumerated) {
+ MOZ_ASSERT(style->HasPercent());
+ minSize = 0;
+ fixedMinSize = &minSize;
+ }
+
+ if (!fixedMinSize) {
+ // Let the caller deal with the "content size" cases.
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s min-isize is indefinite.\n",
+ aType == MIN_ISIZE ? "min" : "pref");
+#endif
+ return NS_UNCONSTRAINEDSIZE;
+ }
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ PhysicalAxis ourInlineAxis =
+ aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ nsIFrame::IntrinsicISizeOffsetData offsets =
+ ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets()
+ : aFrame->IntrinsicBSizeOffsets();
+ nscoord result = 0;
+ nscoord min = 0;
+
+ const nsStyleCoord& maxISize =
+ aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+ result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType,
+ stylePos->mBoxSizing,
+ result, min, *style, fixedMinSize,
+ *style, nullptr, maxISize, aFlags, aAxis);
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsFrame::IndentBy(stderr, gNoiseIndent);
+ static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+ printf_stderr(" %s min-isize is %d twips.\n",
+ aType == MIN_ISIZE ? "min" : "pref", result);
+#endif
+
+ return result;
+}
+
+/* static */ nscoord
+nsLayoutUtils::ComputeCBDependentValue(nscoord aPercentBasis,
+ const nsStyleCoord& aCoord)
+{
+ NS_WARNING_ASSERTION(
+ aPercentBasis != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained width or height; this should only result from very "
+ "large sizes, not attempts at intrinsic size calculation");
+
+ if (aCoord.IsCoordPercentCalcUnit()) {
+ return nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis);
+ }
+ NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None ||
+ aCoord.GetUnit() == eStyleUnit_Auto,
+ "unexpected width value");
+ return 0;
+}
+
+/* static */ nscoord
+nsLayoutUtils::ComputeBSizeDependentValue(
+ nscoord aContainingBlockBSize,
+ const nsStyleCoord& aCoord)
+{
+ // XXXldb Some callers explicitly check aContainingBlockBSize
+ // against NS_AUTOHEIGHT *and* unit against eStyleUnit_Percent or
+ // calc()s containing percents before calling this function.
+ // However, it would be much more likely to catch problems without
+ // the unit conditions.
+ // XXXldb Many callers pass a non-'auto' containing block height when
+ // according to CSS2.1 they should be passing 'auto'.
+ NS_PRECONDITION(NS_AUTOHEIGHT != aContainingBlockBSize ||
+ !aCoord.HasPercent(),
+ "unexpected containing block block-size");
+
+ if (aCoord.IsCoordPercentCalcUnit()) {
+ return nsRuleNode::ComputeCoordPercentCalc(aCoord, aContainingBlockBSize);
+ }
+
+ NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None ||
+ aCoord.GetUnit() == eStyleUnit_Auto,
+ "unexpected block-size value");
+ return 0;
+}
+
+/* static */ void
+nsLayoutUtils::MarkDescendantsDirty(nsIFrame *aSubtreeRoot)
+{
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aSubtreeRoot);
+
+ // dirty descendants, iterating over subtrees that may include
+ // additional subtrees associated with placeholders
+ do {
+ nsIFrame *subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1);
+ subtrees.RemoveElementAt(subtrees.Length() - 1);
+
+ // Mark all descendants dirty (using an nsTArray stack rather than
+ // recursion).
+ // Note that ReflowInput::InitResizeFlags has some similar
+ // code; see comments there for how and why it differs.
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(subtreeRoot);
+
+ do {
+ nsIFrame *f = stack.ElementAt(stack.Length() - 1);
+ stack.RemoveElementAt(stack.Length() - 1);
+
+ f->MarkIntrinsicISizesDirty();
+
+ if (f->GetType() == nsGkAtoms::placeholderFrame) {
+ nsIFrame *oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+
+ nsIFrame::ChildListIterator lists(f);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* kid = childFrames.get();
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+ } while (subtrees.Length() != 0);
+}
+
+/* static */
+void
+nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame* aFrame)
+{
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(aFrame);
+
+ do {
+ nsIFrame* f = stack.ElementAt(stack.Length() - 1);
+ stack.RemoveElementAt(stack.Length() - 1);
+
+ if (!f->HasAnyStateBits(
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ continue;
+ }
+ f->MarkIntrinsicISizesDirty();
+
+ for (nsIFrame::ChildListIterator lists(f); !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* kid : lists.CurrentList()) {
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+}
+
+nsSize
+nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(nscoord minWidth, nscoord minHeight,
+ nscoord maxWidth, nscoord maxHeight,
+ nscoord tentWidth, nscoord tentHeight)
+{
+ // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7:
+
+ if (minWidth > maxWidth)
+ maxWidth = minWidth;
+ if (minHeight > maxHeight)
+ maxHeight = minHeight;
+
+ nscoord heightAtMaxWidth, heightAtMinWidth,
+ widthAtMaxHeight, widthAtMinHeight;
+
+ if (tentWidth > 0) {
+ heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth);
+ if (heightAtMaxWidth < minHeight)
+ heightAtMaxWidth = minHeight;
+ heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth);
+ if (heightAtMinWidth > maxHeight)
+ heightAtMinWidth = maxHeight;
+ } else {
+ heightAtMaxWidth = heightAtMinWidth = NS_CSS_MINMAX(tentHeight, minHeight, maxHeight);
+ }
+
+ if (tentHeight > 0) {
+ widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight);
+ if (widthAtMaxHeight < minWidth)
+ widthAtMaxHeight = minWidth;
+ widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight);
+ if (widthAtMinHeight > maxWidth)
+ widthAtMinHeight = maxWidth;
+ } else {
+ widthAtMaxHeight = widthAtMinHeight = NS_CSS_MINMAX(tentWidth, minWidth, maxWidth);
+ }
+
+ // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths :
+
+ nscoord width, height;
+
+ if (tentWidth > maxWidth) {
+ if (tentHeight > maxHeight) {
+ if (int64_t(maxWidth) * int64_t(tentHeight) <=
+ int64_t(maxHeight) * int64_t(tentWidth)) {
+ width = maxWidth;
+ height = heightAtMaxWidth;
+ } else {
+ width = widthAtMaxHeight;
+ height = maxHeight;
+ }
+ } else {
+ // This also covers "(w > max-width) and (h < min-height)" since in
+ // that case (max-width/w < 1), and with (h < min-height):
+ // max(max-width * h/w, min-height) == min-height
+ width = maxWidth;
+ height = heightAtMaxWidth;
+ }
+ } else if (tentWidth < minWidth) {
+ if (tentHeight < minHeight) {
+ if (int64_t(minWidth) * int64_t(tentHeight) <=
+ int64_t(minHeight) * int64_t(tentWidth)) {
+ width = widthAtMinHeight;
+ height = minHeight;
+ } else {
+ width = minWidth;
+ height = heightAtMinWidth;
+ }
+ } else {
+ // This also covers "(w < min-width) and (h > max-height)" since in
+ // that case (min-width/w > 1), and with (h > max-height):
+ // min(min-width * h/w, max-height) == max-height
+ width = minWidth;
+ height = heightAtMinWidth;
+ }
+ } else {
+ if (tentHeight > maxHeight) {
+ width = widthAtMaxHeight;
+ height = maxHeight;
+ } else if (tentHeight < minHeight) {
+ width = widthAtMinHeight;
+ height = minHeight;
+ } else {
+ width = tentWidth;
+ height = tentHeight;
+ }
+ }
+
+ return nsSize(width, height);
+}
+
+/* static */ nscoord
+nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame,
+ nsRenderingContext* aRenderingContext)
+{
+ NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
+ "should not be container for font size inflation");
+
+ nsIFrame::InlineMinISizeData data;
+ DISPLAY_MIN_WIDTH(aFrame, data.mPrevLines);
+ aFrame->AddInlineMinISize(aRenderingContext, &data);
+ data.ForceBreak();
+ return data.mPrevLines;
+}
+
+/* static */ nscoord
+nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame,
+ nsRenderingContext* aRenderingContext)
+{
+ NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
+ "should not be container for font size inflation");
+
+ nsIFrame::InlinePrefISizeData data;
+ DISPLAY_PREF_WIDTH(aFrame, data.mPrevLines);
+ aFrame->AddInlinePrefISize(aRenderingContext, &data);
+ data.ForceBreak();
+ return data.mPrevLines;
+}
+
+static nscolor
+DarkenColor(nscolor aColor)
+{
+ uint16_t hue, sat, value;
+ uint8_t alpha;
+
+ // convert the RBG to HSV so we can get the lightness (which is the v)
+ NS_RGB2HSV(aColor, hue, sat, value, alpha);
+
+ // The goal here is to send white to black while letting colored
+ // stuff stay colored... So we adopt the following approach.
+ // Something with sat = 0 should end up with value = 0. Something
+ // with a high sat can end up with a high value and it's ok.... At
+ // the same time, we don't want to make things lighter. Do
+ // something simple, since it seems to work.
+ if (value > sat) {
+ value = sat;
+ // convert this color back into the RGB color space.
+ NS_HSV2RGB(aColor, hue, sat, value, alpha);
+ }
+ return aColor;
+}
+
+// Check whether we should darken text/decoration colors. We need to do this if
+// background images and colors are being suppressed, because that means
+// light text will not be visible against the (presumed light-colored) background.
+static bool
+ShouldDarkenColors(nsPresContext* aPresContext)
+{
+ return !aPresContext->GetBackgroundColorDraw() &&
+ !aPresContext->GetBackgroundImageDraw();
+}
+
+nscolor
+nsLayoutUtils::GetColor(nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ nscolor color = aFrame->GetVisitedDependentColor(aProperty);
+ if (ShouldDarkenColors(aFrame->PresContext())) {
+ color = DarkenColor(color);
+ }
+ return color;
+}
+
+gfxFloat
+nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aY, nscoord aAscent)
+{
+ gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxFloat baseline = gfxFloat(aY) + aAscent;
+ gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1);
+ if (!aContext->UserToDevicePixelSnapped(putativeRect, true))
+ return baseline;
+ return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit;
+}
+
+gfxFloat
+nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aX, nscoord aAscent)
+{
+ gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxFloat baseline = gfxFloat(aX) + aAscent;
+ gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1);
+ if (!aContext->UserToDevicePixelSnapped(putativeRect, true)) {
+ return baseline;
+ }
+ return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit;
+}
+
+// Hard limit substring lengths to 8000 characters ... this lets us statically
+// size the cluster buffer array in FindSafeLength
+#define MAX_GFX_TEXT_BUF_SIZE 8000
+
+static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength,
+ uint32_t aMaxChunkLength)
+{
+ if (aLength <= aMaxChunkLength)
+ return aLength;
+
+ int32_t len = aMaxChunkLength;
+
+ // Ensure that we don't break inside a surrogate pair
+ while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
+ len--;
+ }
+ if (len == 0) {
+ // We don't want our caller to go into an infinite loop, so don't
+ // return zero. It's hard to imagine how we could actually get here
+ // unless there are languages that allow clusters of arbitrary size.
+ // If there are and someone feeds us a 500+ character cluster, too
+ // bad.
+ return aMaxChunkLength;
+ }
+ return len;
+}
+
+static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics)
+{
+ return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
+}
+
+nscoord
+nsLayoutUtils::AppUnitWidthOfString(const char16_t *aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget)
+{
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ nscoord width = 0;
+ while (aLength > 0) {
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ width += aFontMetrics.GetWidth(aString, len, aDrawTarget);
+ aLength -= len;
+ aString += len;
+ }
+ return width;
+}
+
+nscoord
+nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
+ uint32_t aLength,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext& aContext)
+{
+ nsPresContext* presContext = aFrame->PresContext();
+ if (presContext->BidiEnabled()) {
+ nsBidiLevel level =
+ nsBidiPresUtils::BidiLevelFromStyle(aFrame->StyleContext());
+ return nsBidiPresUtils::MeasureTextWidth(aString, aLength, level,
+ presContext, aContext,
+ aFontMetrics);
+ }
+ aFontMetrics.SetTextRunRTL(false);
+ aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical());
+ aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation);
+ return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
+ aContext.GetDrawTarget());
+}
+
+bool
+nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget,
+ nscoord aWidth)
+{
+ const char16_t *string = aString.get();
+ uint32_t length = aString.Length();
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ nscoord width = 0;
+ while (length > 0) {
+ int32_t len = FindSafeLength(string, length, maxChunkLength);
+ width += aFontMetrics.GetWidth(string, len, aDrawTarget);
+ if (width > aWidth) {
+ return true;
+ }
+ length -= len;
+ string += len;
+ }
+ return false;
+}
+
+nsBoundingMetrics
+nsLayoutUtils::AppUnitBoundsOfString(const char16_t* aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget)
+{
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ // Assign directly in the first iteration. This ensures that
+ // negative ascent/descent can be returned and the left bearing
+ // is properly initialized.
+ nsBoundingMetrics totalMetrics =
+ aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
+ aLength -= len;
+ aString += len;
+
+ while (aLength > 0) {
+ len = FindSafeLength(aString, aLength, maxChunkLength);
+ nsBoundingMetrics metrics =
+ aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
+ totalMetrics += metrics;
+ aLength -= len;
+ aString += len;
+ }
+ return totalMetrics;
+}
+
+void
+nsLayoutUtils::DrawString(const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext* aContext,
+ const char16_t* aString,
+ int32_t aLength,
+ nsPoint aPoint,
+ nsStyleContext* aStyleContext)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // If caller didn't pass a style context, use the frame's.
+ if (!aStyleContext) {
+ aStyleContext = aFrame->StyleContext();
+ }
+ aFontMetrics.SetVertical(WritingMode(aStyleContext).IsVertical());
+ aFontMetrics.SetTextOrientation(
+ aStyleContext->StyleVisibility()->mTextOrientation);
+
+ nsPresContext* presContext = aFrame->PresContext();
+ if (presContext->BidiEnabled()) {
+ nsBidiLevel level =
+ nsBidiPresUtils::BidiLevelFromStyle(aStyleContext);
+ rv = nsBidiPresUtils::RenderText(aString, aLength, level,
+ presContext, *aContext,
+ aContext->GetDrawTarget(), aFontMetrics,
+ aPoint.x, aPoint.y);
+ }
+ if (NS_FAILED(rv))
+ {
+ aFontMetrics.SetTextRunRTL(false);
+ DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext);
+ }
+}
+
+void
+nsLayoutUtils::DrawUniDirString(const char16_t* aString,
+ uint32_t aLength,
+ nsPoint aPoint,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext& aContext)
+{
+ nscoord x = aPoint.x;
+ nscoord y = aPoint.y;
+
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ if (aLength <= maxChunkLength) {
+ aFontMetrics.DrawString(aString, aLength, x, y, &aContext,
+ aContext.GetDrawTarget());
+ return;
+ }
+
+ bool isRTL = aFontMetrics.GetTextRunRTL();
+
+ // If we're drawing right to left, we must start at the end.
+ if (isRTL) {
+ x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
+ aContext.GetDrawTarget());
+ }
+
+ while (aLength > 0) {
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ nscoord width = aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget());
+ if (isRTL) {
+ x -= width;
+ }
+ aFontMetrics.DrawString(aString, len, x, y, &aContext,
+ aContext.GetDrawTarget());
+ if (!isRTL) {
+ x += width;
+ }
+ aLength -= len;
+ aString += len;
+ }
+}
+
+/* static */ void
+nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame,
+ nsRenderingContext* aContext,
+ const nsRect& aTextRect,
+ const nsRect& aDirtyRect,
+ const nscolor& aForegroundColor,
+ TextShadowCallback aCallback,
+ void* aCallbackData)
+{
+ const nsStyleText* textStyle = aFrame->StyleText();
+ if (!textStyle->HasTextShadow())
+ return;
+
+ // Text shadow happens with the last value being painted at the back,
+ // ie. it is painted first.
+ gfxContext* aDestCtx = aContext->ThebesContext();
+ for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
+ nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1);
+ nsPoint shadowOffset(shadowDetails->mXOffset,
+ shadowDetails->mYOffset);
+ nscoord blurRadius = std::max(shadowDetails->mRadius, 0);
+
+ nsRect shadowRect(aTextRect);
+ shadowRect.MoveBy(shadowOffset);
+
+ nsPresContext* presCtx = aFrame->PresContext();
+ nsContextBoxBlur contextBoxBlur;
+ gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
+ presCtx->AppUnitsPerDevPixel(),
+ aDestCtx, aDirtyRect, nullptr);
+ if (!shadowContext)
+ continue;
+
+ nscolor shadowColor;
+ if (shadowDetails->mHasColor)
+ shadowColor = shadowDetails->mColor;
+ else
+ shadowColor = aForegroundColor;
+
+ // Conjure an nsRenderingContext from a gfxContext for drawing the text
+ // to blur.
+ nsRenderingContext renderingContext(shadowContext);
+
+ aDestCtx->Save();
+ aDestCtx->NewPath();
+ aDestCtx->SetColor(Color::FromABGR(shadowColor));
+
+ // The callback will draw whatever we want to blur as a shadow.
+ aCallback(&renderingContext, shadowOffset, shadowColor, aCallbackData);
+
+ contextBoxBlur.DoPaint();
+ aDestCtx->Restore();
+ }
+}
+
+/* static */ nscoord
+nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
+ nscoord aLineHeight,
+ bool aIsInverted)
+{
+ nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent()
+ : aFontMetrics->MaxAscent();
+ nscoord fontHeight = aFontMetrics->MaxHeight();
+
+ nscoord leading = aLineHeight - fontHeight;
+ return fontAscent + leading/2;
+}
+
+
+/* static */ bool
+nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode,
+ const nsIFrame* aFrame, nscoord* aResult)
+{
+ LinePosition position;
+ if (!GetFirstLinePosition(aWritingMode, aFrame, &position))
+ return false;
+ *aResult = position.mBaseline;
+ return true;
+}
+
+/* static */ bool
+nsLayoutUtils::GetFirstLinePosition(WritingMode aWM,
+ const nsIFrame* aFrame,
+ LinePosition* aResult)
+{
+ const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast<nsIFrame*>(aFrame));
+ if (!block) {
+ // For the first-line baseline we also have to check for a table, and if
+ // so, use the baseline of its first row.
+ nsIAtom* fType = aFrame->GetType();
+ if (fType == nsGkAtoms::tableWrapperFrame ||
+ fType == nsGkAtoms::flexContainerFrame ||
+ fType == nsGkAtoms::gridContainerFrame) {
+ if ((fType == nsGkAtoms::gridContainerFrame &&
+ aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) ||
+ (fType == nsGkAtoms::flexContainerFrame &&
+ aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) ||
+ (fType == nsGkAtoms::tableWrapperFrame &&
+ static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() == 0)) {
+ // empty grid/flex/table container
+ aResult->mBStart = 0;
+ aResult->mBaseline = aFrame->SynthesizeBaselineBOffsetFromBorderBox(aWM,
+ BaselineSharingGroup::eFirst);
+ aResult->mBEnd = aFrame->BSize(aWM);
+ return true;
+ }
+ aResult->mBStart = 0;
+ aResult->mBaseline = aFrame->GetLogicalBaseline(aWM);
+ // This is what we want for the list bullet caller; not sure if
+ // other future callers will want the same.
+ aResult->mBEnd = aFrame->BSize(aWM);
+ return true;
+ }
+
+ // For first-line baselines, we have to consider scroll frames.
+ if (fType == nsGkAtoms::scrollFrame) {
+ nsIScrollableFrame *sFrame = do_QueryFrame(const_cast<nsIFrame*>(aFrame));
+ if (!sFrame) {
+ NS_NOTREACHED("not scroll frame");
+ }
+ LinePosition kidPosition;
+ if (GetFirstLinePosition(aWM,
+ sFrame->GetScrolledFrame(), &kidPosition)) {
+ // Consider only the border and padding that contributes to the
+ // kid's position, not the scrolling, so we get the initial
+ // position.
+ *aResult = kidPosition +
+ aFrame->GetLogicalUsedBorderAndPadding(aWM).BStart(aWM);
+ return true;
+ }
+ return false;
+ }
+
+ if (fType == nsGkAtoms::fieldSetFrame) {
+ LinePosition kidPosition;
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ // kid might be a legend frame here, but that's ok.
+ if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
+ *aResult = kidPosition +
+ kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
+ return true;
+ }
+ return false;
+ }
+
+ // No baseline.
+ return false;
+ }
+
+ for (nsBlockFrame::ConstLineIterator line = block->LinesBegin(),
+ line_end = block->LinesEnd();
+ line != line_end; ++line) {
+ if (line->IsBlock()) {
+ nsIFrame *kid = line->mFirstChild;
+ LinePosition kidPosition;
+ if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
+ //XXX Not sure if this is the correct value to use for container
+ // width here. It will only be used in vertical-rl layout,
+ // which we don't have full support and testing for yet.
+ const nsSize& containerSize = line->mContainerSize;
+ *aResult = kidPosition +
+ kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ return true;
+ }
+ } else {
+ // XXX Is this the right test? We have some bogus empty lines
+ // floating around, but IsEmpty is perhaps too weak.
+ if (line->BSize() != 0 || !line->IsEmpty()) {
+ nscoord bStart = line->BStart();
+ aResult->mBStart = bStart;
+ aResult->mBaseline = bStart + line->GetLogicalAscent();
+ aResult->mBEnd = bStart + line->BSize();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* static */ bool
+nsLayoutUtils::GetLastLineBaseline(WritingMode aWM,
+ const nsIFrame* aFrame, nscoord* aResult)
+{
+ const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast<nsIFrame*>(aFrame));
+ if (!block)
+ // No baseline. (We intentionally don't descend into scroll frames.)
+ return false;
+
+ for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(),
+ line_end = block->LinesREnd();
+ line != line_end; ++line) {
+ if (line->IsBlock()) {
+ nsIFrame *kid = line->mFirstChild;
+ nscoord kidBaseline;
+ const nsSize& containerSize = line->mContainerSize;
+ if (GetLastLineBaseline(aWM, kid, &kidBaseline)) {
+ // Ignore relative positioning for baseline calculations
+ *aResult = kidBaseline +
+ kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ return true;
+ } else if (kid->GetType() == nsGkAtoms::scrollFrame) {
+ // Use the bottom of the scroll frame.
+ // XXX CSS2.1 really doesn't say what to do here.
+ *aResult = kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM) +
+ kid->BSize(aWM);
+ return true;
+ }
+ } else {
+ // XXX Is this the right test? We have some bogus empty lines
+ // floating around, but IsEmpty is perhaps too weak.
+ if (line->BSize() != 0 || !line->IsEmpty()) {
+ *aResult = line->BStart() + line->GetLogicalAscent();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static nscoord
+CalculateBlockContentBEnd(WritingMode aWM, nsBlockFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame, "null ptr");
+
+ nscoord contentBEnd = 0;
+
+ for (nsBlockFrame::LineIterator line = aFrame->LinesBegin(),
+ line_end = aFrame->LinesEnd();
+ line != line_end; ++line) {
+ if (line->IsBlock()) {
+ nsIFrame* child = line->mFirstChild;
+ const nsSize& containerSize = line->mContainerSize;
+ nscoord offset =
+ child->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ contentBEnd =
+ std::max(contentBEnd,
+ nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset);
+ }
+ else {
+ contentBEnd = std::max(contentBEnd, line->BEnd());
+ }
+ }
+ return contentBEnd;
+}
+
+/* static */ nscoord
+nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame, "null ptr");
+
+ nscoord contentBEnd = aFrame->BSize(aWM);
+
+ // We want scrollable overflow rather than visual because this
+ // calculation is intended to affect layout.
+ LogicalSize overflowSize(aWM, aFrame->GetScrollableOverflowRect().Size());
+ if (overflowSize.BSize(aWM) > contentBEnd) {
+ nsIFrame::ChildListIDs skip(nsIFrame::kOverflowList |
+ nsIFrame::kExcessOverflowContainersList |
+ nsIFrame::kOverflowOutOfFlowList);
+ nsBlockFrame* blockFrame = GetAsBlock(aFrame);
+ if (blockFrame) {
+ contentBEnd =
+ std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame));
+ skip |= nsIFrame::kPrincipalList;
+ }
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ if (!skip.Contains(lists.CurrentID())) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* child = childFrames.get();
+ nscoord offset =
+ child->GetLogicalNormalPosition(aWM,
+ aFrame->GetSize()).B(aWM);
+ contentBEnd = std::max(contentBEnd,
+ CalculateContentBEnd(aWM, child) + offset);
+ }
+ }
+ }
+ }
+ return contentBEnd;
+}
+
+/* static */ nsIFrame*
+nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame)
+{
+ nsIFrame* layer;
+ for (layer = aFrame; layer; layer = layer->GetParent()) {
+ if (layer->IsAbsPosContainingBlock() ||
+ (layer->GetParent() &&
+ layer->GetParent()->GetType() == nsGkAtoms::scrollFrame))
+ break;
+ }
+ if (layer)
+ return layer;
+ return aFrame->PresContext()->PresShell()->FrameManager()->GetRootFrame();
+}
+
+SamplingFilter
+nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame)
+{
+ SamplingFilter defaultFilter = SamplingFilter::GOOD;
+ nsStyleContext *sc;
+ if (nsCSSRendering::IsCanvasFrame(aForFrame)) {
+ nsCSSRendering::FindBackground(aForFrame, &sc);
+ } else {
+ sc = aForFrame->StyleContext();
+ }
+
+ switch (sc->StyleVisibility()->mImageRendering) {
+ case NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED:
+ return SamplingFilter::POINT;
+ case NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY:
+ return SamplingFilter::LINEAR;
+ case NS_STYLE_IMAGE_RENDERING_CRISPEDGES:
+ return SamplingFilter::POINT;
+ default:
+ return defaultFilter;
+ }
+}
+
+/**
+ * Given an image being drawn into an appunit coordinate system, and
+ * a point in that coordinate system, map the point back into image
+ * pixel space.
+ * @param aSize the size of the image, in pixels
+ * @param aDest the rectangle that the image is being mapped into
+ * @param aPt a point in the same coordinate system as the rectangle
+ */
+static gfxPoint
+MapToFloatImagePixels(const gfxSize& aSize,
+ const gfxRect& aDest, const gfxPoint& aPt)
+{
+ return gfxPoint(((aPt.x - aDest.X())*aSize.width)/aDest.Width(),
+ ((aPt.y - aDest.Y())*aSize.height)/aDest.Height());
+}
+
+/**
+ * Given an image being drawn into an pixel-based coordinate system, and
+ * a point in image space, map the point into the pixel-based coordinate
+ * system.
+ * @param aSize the size of the image, in pixels
+ * @param aDest the rectangle that the image is being mapped into
+ * @param aPt a point in image space
+ */
+static gfxPoint
+MapToFloatUserPixels(const gfxSize& aSize,
+ const gfxRect& aDest, const gfxPoint& aPt)
+{
+ return gfxPoint(aPt.x*aDest.Width()/aSize.width + aDest.X(),
+ aPt.y*aDest.Height()/aSize.height + aDest.Y());
+}
+
+/* static */ gfxRect
+nsLayoutUtils::RectToGfxRect(const nsRect& aRect, int32_t aAppUnitsPerDevPixel)
+{
+ return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.y) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.width) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.height) / aAppUnitsPerDevPixel);
+}
+
+struct SnappedImageDrawingParameters {
+ // A transform from image space to device space.
+ gfxMatrix imageSpaceToDeviceSpace;
+ // The size at which the image should be drawn (which may not be its
+ // intrinsic size due to, for example, HQ scaling).
+ nsIntSize size;
+ // The region in tiled image space which will be drawn, with an associated
+ // region to which sampling should be restricted.
+ ImageRegion region;
+ // The default viewport size for SVG images, which we use unless a different
+ // one has been explicitly specified. This is the same as |size| except that
+ // it does not take into account any transformation on the gfxContext we're
+ // drawing to - for example, CSS transforms are not taken into account.
+ CSSIntSize svgViewportSize;
+ // Whether there's anything to draw at all.
+ bool shouldDraw;
+
+ SnappedImageDrawingParameters()
+ : region(ImageRegion::Empty())
+ , shouldDraw(false)
+ {}
+
+ SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace,
+ const nsIntSize& aSize,
+ const ImageRegion& aRegion,
+ const CSSIntSize& aSVGViewportSize)
+ : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace)
+ , size(aSize)
+ , region(aRegion)
+ , svgViewportSize(aSVGViewportSize)
+ , shouldDraw(true)
+ {}
+};
+
+/**
+ * Given two axis-aligned rectangles, returns the transformation that maps the
+ * first onto the second.
+ *
+ * @param aFrom The rect to be transformed.
+ * @param aTo The rect that aFrom should be mapped onto by the transformation.
+ */
+static gfxMatrix
+TransformBetweenRects(const gfxRect& aFrom, const gfxRect& aTo)
+{
+ gfxSize scale(aTo.width / aFrom.width,
+ aTo.height / aFrom.height);
+ gfxPoint translation(aTo.x - aFrom.x * scale.width,
+ aTo.y - aFrom.y * scale.height);
+ return gfxMatrix(scale.width, 0, 0, scale.height,
+ translation.x, translation.y);
+}
+
+static nsRect
+TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect)
+{
+ nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft();
+ return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width,
+ distance.y / aAnyTile.height * aAnyTile.height);
+}
+
+static gfxFloat
+StableRound(gfxFloat aValue)
+{
+ // Values slightly less than 0.5 should round up like 0.5 would; we're
+ // assuming they were meant to be 0.5.
+ return floor(aValue + 0.5001);
+}
+
+static gfxPoint
+StableRound(const gfxPoint& aPoint)
+{
+ return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y));
+}
+
+/**
+ * Given a set of input parameters, compute certain output parameters
+ * for drawing an image with the image snapping algorithm.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ *
+ * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters
+ */
+static SnappedImageDrawingParameters
+ComputeSnappedImageDrawingParameters(gfxContext* aCtx,
+ int32_t aAppUnitsPerDevPixel,
+ const nsRect aDest,
+ const nsRect aFill,
+ const nsPoint aAnchor,
+ const nsRect aDirty,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ uint32_t aImageFlags,
+ ExtendMode aExtendMode)
+{
+ if (aDest.IsEmpty() || aFill.IsEmpty())
+ return SnappedImageDrawingParameters();
+
+ // Avoid unnecessarily large offsets.
+ bool doTile = !aDest.Contains(aFill);
+ nsRect appUnitDest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty))
+ : aDest;
+ nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft());
+
+ gfxRect devPixelDest =
+ nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel);
+ gfxRect devPixelFill =
+ nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
+ gfxRect devPixelDirty =
+ nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
+
+ gfxMatrix currentMatrix = aCtx->CurrentMatrix();
+ gfxRect fill = devPixelFill;
+ gfxRect dest = devPixelDest;
+ bool didSnap;
+ // Snap even if we have a scale in the context. But don't snap if
+ // we have something that's not translation+scale, or if the scale flips in
+ // the X or Y direction, because snapped image drawing can't handle that yet.
+ if (!currentMatrix.HasNonAxisAlignedTransform() &&
+ currentMatrix._11 > 0.0 && currentMatrix._22 > 0.0 &&
+ aCtx->UserToDevicePixelSnapped(fill, true) &&
+ aCtx->UserToDevicePixelSnapped(dest, true)) {
+ // We snapped. On this code path, |fill| and |dest| take into account
+ // currentMatrix's transform.
+ didSnap = true;
+ } else {
+ // We didn't snap. On this code path, |fill| and |dest| do not take into
+ // account currentMatrix's transform.
+ didSnap = false;
+ fill = devPixelFill;
+ dest = devPixelDest;
+ }
+
+ // If we snapped above, |dest| already takes into account |currentMatrix|'s scale
+ // and has integer coordinates. If not, we need these properties to compute
+ // the optimal drawn image size, so compute |snappedDestSize| here.
+ gfxSize snappedDestSize = dest.Size();
+ if (!didSnap) {
+ gfxSize scaleFactors = currentMatrix.ScaleFactors(true);
+ snappedDestSize.Scale(scaleFactors.width, scaleFactors.height);
+ snappedDestSize.width = NS_round(snappedDestSize.width);
+ snappedDestSize.height = NS_round(snappedDestSize.height);
+ }
+
+ // We need to be sure that this is at least one pixel in width and height,
+ // or we'll end up drawing nothing even if we have a nonempty fill.
+ snappedDestSize.width = std::max(snappedDestSize.width, 1.0);
+ snappedDestSize.height = std::max(snappedDestSize.height, 1.0);
+
+ // Bail if we're not going to end up drawing anything.
+ if (fill.IsEmpty() || snappedDestSize.IsEmpty()) {
+ return SnappedImageDrawingParameters();
+ }
+
+ nsIntSize intImageSize =
+ aImage->OptimalImageSizeForDest(snappedDestSize,
+ imgIContainer::FRAME_CURRENT,
+ aSamplingFilter, aImageFlags);
+ gfxSize imageSize(intImageSize.width, intImageSize.height);
+
+ // XXX(seth): May be buggy; see bug 1151016.
+ CSSIntSize svgViewportSize = currentMatrix.IsIdentity()
+ ? CSSIntSize(intImageSize.width, intImageSize.height)
+ : CSSIntSize::Truncate(devPixelDest.width, devPixelDest.height);
+
+ // Compute the set of pixels that would be sampled by an ideal rendering
+ gfxPoint subimageTopLeft =
+ MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft());
+ gfxPoint subimageBottomRight =
+ MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.BottomRight());
+ gfxRect subimage;
+ subimage.MoveTo(NSToIntFloor(subimageTopLeft.x),
+ NSToIntFloor(subimageTopLeft.y));
+ subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x,
+ NSToIntCeil(subimageBottomRight.y) - subimage.y);
+
+ if (subimage.IsEmpty()) {
+ // Bail if the subimage is empty (we're not going to be drawing anything).
+ return SnappedImageDrawingParameters();
+ }
+
+ gfxMatrix transform;
+ gfxMatrix invTransform;
+
+ bool anchorAtUpperLeft = anchor.x == appUnitDest.x &&
+ anchor.y == appUnitDest.y;
+ bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest);
+ if (anchorAtUpperLeft && exactlyOneImageCopy) {
+ // The simple case: we can ignore the anchor point and compute the
+ // transformation from the sampled region (the subimage) to the fill rect.
+ // This approach is preferable when it works since it tends to produce
+ // less numerical error.
+ transform = TransformBetweenRects(subimage, fill);
+ invTransform = TransformBetweenRects(fill, subimage);
+ } else {
+ // The more complicated case: we compute the transformation from the
+ // image rect positioned at the image space anchor point to the dest rect
+ // positioned at the device space anchor point.
+
+ // Compute the anchor point in both device space and image space. This
+ // code assumes that pixel-based devices have one pixel per device unit!
+ gfxPoint anchorPoint(gfxFloat(anchor.x)/aAppUnitsPerDevPixel,
+ gfxFloat(anchor.y)/aAppUnitsPerDevPixel);
+ gfxPoint imageSpaceAnchorPoint =
+ MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint);
+
+ if (didSnap) {
+ imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint);
+ anchorPoint = imageSpaceAnchorPoint;
+ anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint);
+ anchorPoint = currentMatrix.Transform(anchorPoint);
+ anchorPoint = StableRound(anchorPoint);
+ }
+
+ // Compute an unsnapped version of the dest rect's size. We continue to
+ // follow the pattern that we take |currentMatrix| into account only if
+ // |didSnap| is true.
+ gfxSize unsnappedDestSize
+ = didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors(true)
+ : devPixelDest.Size();
+
+ gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize);
+ gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize);
+
+ // Calculate anchoredDestRect with snapped fill rect when the devPixelFill rect
+ // corresponds to just a single tile in that direction
+ if (fill.Width() != devPixelFill.Width() &&
+ devPixelDest.x == devPixelFill.x &&
+ devPixelDest.XMost() == devPixelFill.XMost()) {
+ anchoredDestRect.width = fill.width;
+ }
+ if (fill.Height() != devPixelFill.Height() &&
+ devPixelDest.y == devPixelFill.y &&
+ devPixelDest.YMost() == devPixelFill.YMost()) {
+ anchoredDestRect.height = fill.height;
+ }
+
+ transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect);
+ invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect);
+ }
+
+ // If the transform is not a straight translation by integers, then
+ // filtering will occur, and restricting the fill rect to the dirty rect
+ // would change the values computed for edge pixels, which we can't allow.
+ // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not
+ // produce pixel-aligned coordinates, which would also break the values
+ // computed for edge pixels.
+ if (didSnap && !invTransform.HasNonIntegerTranslation()) {
+ // This form of Transform is safe to call since non-axis-aligned
+ // transforms wouldn't be snapped.
+ devPixelDirty = currentMatrix.Transform(devPixelDirty);
+ devPixelDirty.RoundOut();
+ fill = fill.Intersect(devPixelDirty);
+ }
+ if (fill.IsEmpty())
+ return SnappedImageDrawingParameters();
+
+ gfxRect imageSpaceFill(didSnap ? invTransform.Transform(fill)
+ : invTransform.TransformBounds(fill));
+
+ // If we didn't snap, we need to post-multiply the matrix on the context to
+ // get the final matrix we'll draw with, because we didn't take it into
+ // account when computing the matrices above.
+ if (!didSnap) {
+ transform = transform * currentMatrix;
+ }
+
+ ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP)
+ ? ExtendMode::CLAMP
+ : aExtendMode;
+ // We were passed in the default extend mode but need to tile.
+ if (extendMode == ExtendMode::CLAMP && doTile) {
+ MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP));
+ extendMode = ExtendMode::REPEAT;
+ }
+
+ ImageRegion region =
+ ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage, extendMode);
+
+ return SnappedImageDrawingParameters(transform, intImageSize,
+ region, svgViewportSize);
+}
+
+static DrawResult
+DrawImageInternal(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ const SVGImageContext* aSVGContext,
+ uint32_t aImageFlags,
+ ExtendMode aExtendMode = ExtendMode::CLAMP)
+{
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (aPresContext->Type() == nsPresContext::eContext_Print) {
+ // We want vector images to be passed on as vector commands, not a raster
+ // image.
+ aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
+ }
+ if (aDest.Contains(aFill)) {
+ aImageFlags |= imgIContainer::FLAG_CLAMP;
+ }
+ int32_t appUnitsPerDevPixel =
+ aPresContext->AppUnitsPerDevPixel();
+
+ SnappedImageDrawingParameters params =
+ ComputeSnappedImageDrawingParameters(&aContext, appUnitsPerDevPixel, aDest,
+ aFill, aAnchor, aDirty, aImage,
+ aSamplingFilter, aImageFlags, aExtendMode);
+
+ if (!params.shouldDraw) {
+ return result;
+ }
+
+ {
+ gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
+
+ RefPtr<gfxContext> destCtx = &aContext;
+
+ destCtx->SetMatrix(params.imageSpaceToDeviceSpace);
+
+ Maybe<SVGImageContext> svgContext = ToMaybe(aSVGContext);
+ if (!svgContext) {
+ // Use the default viewport.
+ svgContext = Some(SVGImageContext(params.svgViewportSize, Nothing()));
+ }
+
+ result = aImage->Draw(destCtx, params.size, params.region,
+ imgIContainer::FRAME_CURRENT, aSamplingFilter,
+ svgContext, aImageFlags);
+
+ }
+
+ return result;
+}
+
+/* static */ DrawResult
+nsLayoutUtils::DrawSingleUnscaledImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsPoint& aDest,
+ const nsRect* aDirty,
+ uint32_t aImageFlags,
+ const nsRect* aSourceArea)
+{
+ CSSIntSize imageSize;
+ aImage->GetWidth(&imageSize.width);
+ aImage->GetHeight(&imageSize.height);
+ if (imageSize.width < 1 || imageSize.height < 1) {
+ NS_WARNING("Image width or height is non-positive");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ nsSize size(CSSPixel::ToAppUnits(imageSize));
+ nsRect source;
+ if (aSourceArea) {
+ source = *aSourceArea;
+ } else {
+ source.SizeTo(size);
+ }
+
+ nsRect dest(aDest - source.TopLeft(), size);
+ nsRect fill(aDest, source.Size());
+ // Ensure that only a single image tile is drawn. If aSourceArea extends
+ // outside the image bounds, we want to honor the aSourceArea-to-aDest
+ // translation but we don't want to actually tile the image.
+ fill.IntersectRect(fill, dest);
+ return DrawImageInternal(aContext, aPresContext,
+ aImage, aSamplingFilter,
+ dest, fill, aDest, aDirty ? *aDirty : dest,
+ nullptr, aImageFlags);
+}
+
+/* static */ DrawResult
+nsLayoutUtils::DrawSingleImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aDirty,
+ const SVGImageContext* aSVGContext,
+ uint32_t aImageFlags,
+ const nsPoint* aAnchorPoint,
+ const nsRect* aSourceArea)
+{
+ nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel();
+ CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(aImage, aDest.Size()));
+ if (pixelImageSize.width < 1 || pixelImageSize.height < 1) {
+ NS_WARNING("Image width or height is non-positive");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize));
+ nsRect source;
+ nsCOMPtr<imgIContainer> image;
+ if (aSourceArea) {
+ source = *aSourceArea;
+ nsIntRect subRect(source.x, source.y, source.width, source.height);
+ subRect.ScaleInverseRoundOut(appUnitsPerCSSPixel);
+ image = ImageOps::Clip(aImage, subRect);
+
+ nsRect imageRect;
+ imageRect.SizeTo(imageSize);
+ nsRect clippedSource = imageRect.Intersect(source);
+
+ source -= clippedSource.TopLeft();
+ imageSize = clippedSource.Size();
+ } else {
+ source.SizeTo(imageSize);
+ image = aImage;
+ }
+
+ nsRect dest = GetWholeImageDestination(imageSize, source, aDest);
+
+ // Ensure that only a single image tile is drawn. If aSourceArea extends
+ // outside the image bounds, we want to honor the aSourceArea-to-aDest
+ // transform but we don't want to actually tile the image.
+ nsRect fill;
+ fill.IntersectRect(aDest, dest);
+ return DrawImageInternal(aContext, aPresContext, image,
+ aSamplingFilter, dest, fill,
+ aAnchorPoint ? *aAnchorPoint : fill.TopLeft(),
+ aDirty, aSVGContext, aImageFlags);
+}
+
+/* static */ void
+nsLayoutUtils::ComputeSizeForDrawing(imgIContainer *aImage,
+ CSSIntSize& aImageSize, /*outparam*/
+ nsSize& aIntrinsicRatio, /*outparam*/
+ bool& aGotWidth, /*outparam*/
+ bool& aGotHeight /*outparam*/)
+{
+ aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width));
+ aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height));
+ bool gotRatio = NS_SUCCEEDED(aImage->GetIntrinsicRatio(&aIntrinsicRatio));
+
+ if (!(aGotWidth && aGotHeight) && !gotRatio) {
+ // We hit an error (say, because the image failed to load or couldn't be
+ // decoded) and should return zero size.
+ aGotWidth = aGotHeight = true;
+ aImageSize = CSSIntSize(0, 0);
+ aIntrinsicRatio = nsSize(0, 0);
+ }
+}
+
+/* static */ CSSIntSize
+nsLayoutUtils::ComputeSizeForDrawingWithFallback(imgIContainer* aImage,
+ const nsSize& aFallbackSize)
+{
+ CSSIntSize imageSize;
+ nsSize imageRatio;
+ bool gotHeight, gotWidth;
+ ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight);
+
+ // If we didn't get both width and height, try to compute them using the
+ // intrinsic ratio of the image.
+ if (gotWidth != gotHeight) {
+ if (!gotWidth) {
+ if (imageRatio.height != 0) {
+ imageSize.width =
+ NSCoordSaturatingNonnegativeMultiply(imageSize.height,
+ float(imageRatio.width) /
+ float(imageRatio.height));
+ gotWidth = true;
+ }
+ } else {
+ if (imageRatio.width != 0) {
+ imageSize.height =
+ NSCoordSaturatingNonnegativeMultiply(imageSize.width,
+ float(imageRatio.height) /
+ float(imageRatio.width));
+ gotHeight = true;
+ }
+ }
+ }
+
+ // If we still don't have a width or height, just use the fallback size the
+ // caller provided.
+ if (!gotWidth) {
+ imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width);
+ }
+ if (!gotHeight) {
+ imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height);
+ }
+
+ return imageSize;
+}
+
+/* static */ DrawResult
+nsLayoutUtils::DrawBackgroundImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const CSSIntSize& aImageSize,
+ SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsSize& aRepeatSize,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ uint32_t aImageFlags,
+ ExtendMode aExtendMode)
+{
+ PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ SVGImageContext svgContext(aImageSize, Nothing());
+
+ /* Fast path when there is no need for image spacing */
+ if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) {
+ return DrawImageInternal(aContext, aPresContext, aImage,
+ aSamplingFilter, aDest, aFill, aAnchor,
+ aDirty, &svgContext, aImageFlags, aExtendMode);
+ }
+
+ nsPoint firstTilePos = aDest.TopLeft() +
+ nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) * aRepeatSize.width,
+ NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) * aRepeatSize.height);
+ for (int32_t i = firstTilePos.x; i < aFill.XMost(); i += aRepeatSize.width) {
+ for (int32_t j = firstTilePos.y; j < aFill.YMost(); j += aRepeatSize.height) {
+ nsRect dest(i, j, aDest.width, aDest.height);
+ DrawResult result = DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
+ dest, dest, aAnchor, aDirty, &svgContext,
+ aImageFlags, ExtendMode::CLAMP);
+ if (result != DrawResult::SUCCESS) {
+ return result;
+ }
+ }
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+/* static */ DrawResult
+nsLayoutUtils::DrawImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ uint32_t aImageFlags)
+{
+ return DrawImageInternal(aContext, aPresContext, aImage,
+ aSamplingFilter, aDest, aFill, aAnchor,
+ aDirty, nullptr, aImageFlags);
+}
+
+/* static */ nsRect
+nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize,
+ const nsRect& aImageSourceArea,
+ const nsRect& aDestArea)
+{
+ double scaleX = double(aDestArea.width)/aImageSourceArea.width;
+ double scaleY = double(aDestArea.height)/aImageSourceArea.height;
+ nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x*scaleX);
+ nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y*scaleY);
+ nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width*scaleX);
+ nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height*scaleY);
+ return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY),
+ nsSize(wholeSizeX, wholeSizeY));
+}
+
+/* static */ already_AddRefed<imgIContainer>
+nsLayoutUtils::OrientImage(imgIContainer* aContainer,
+ const nsStyleImageOrientation& aOrientation)
+{
+ MOZ_ASSERT(aContainer, "Should have an image container");
+ nsCOMPtr<imgIContainer> img(aContainer);
+
+ if (aOrientation.IsFromImage()) {
+ img = ImageOps::Orient(img, img->GetOrientation());
+ } else if (!aOrientation.IsDefault()) {
+ Angle angle = aOrientation.Angle();
+ Flip flip = aOrientation.IsFlipped() ? Flip::Horizontal
+ : Flip::Unflipped;
+ img = ImageOps::Orient(img, Orientation(angle, flip));
+ }
+
+ return img.forget();
+}
+
+static bool NonZeroStyleCoord(const nsStyleCoord& aCoord)
+{
+ if (aCoord.IsCoordPercentCalcUnit()) {
+ // Since negative results are clamped to 0, check > 0.
+ return nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) > 0 ||
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) > 0;
+ }
+
+ return true;
+}
+
+/* static */ bool
+nsLayoutUtils::HasNonZeroCorner(const nsStyleCorners& aCorners)
+{
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ if (NonZeroStyleCoord(aCorners.Get(corner)))
+ return true;
+ }
+ return false;
+}
+
+// aCorner is a "full corner" value, i.e. NS_CORNER_TOP_LEFT etc
+static bool IsCornerAdjacentToSide(uint8_t aCorner, css::Side aSide)
+{
+ static_assert((int)NS_SIDE_TOP == NS_CORNER_TOP_LEFT, "Check for Full Corner");
+ static_assert((int)NS_SIDE_RIGHT == NS_CORNER_TOP_RIGHT, "Check for Full Corner");
+ static_assert((int)NS_SIDE_BOTTOM == NS_CORNER_BOTTOM_RIGHT, "Check for Full Corner");
+ static_assert((int)NS_SIDE_LEFT == NS_CORNER_BOTTOM_LEFT, "Check for Full Corner");
+ static_assert((int)NS_SIDE_TOP == ((NS_CORNER_TOP_RIGHT - 1)&3), "Check for Full Corner");
+ static_assert((int)NS_SIDE_RIGHT == ((NS_CORNER_BOTTOM_RIGHT - 1)&3), "Check for Full Corner");
+ static_assert((int)NS_SIDE_BOTTOM == ((NS_CORNER_BOTTOM_LEFT - 1)&3), "Check for Full Corner");
+ static_assert((int)NS_SIDE_LEFT == ((NS_CORNER_TOP_LEFT - 1)&3), "Check for Full Corner");
+
+ return aSide == aCorner || aSide == ((aCorner - 1)&3);
+}
+
+/* static */ bool
+nsLayoutUtils::HasNonZeroCornerOnSide(const nsStyleCorners& aCorners,
+ css::Side aSide)
+{
+ static_assert(NS_CORNER_TOP_LEFT_X/2 == NS_CORNER_TOP_LEFT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_TOP_LEFT_Y/2 == NS_CORNER_TOP_LEFT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_TOP_RIGHT_X/2 == NS_CORNER_TOP_RIGHT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_TOP_RIGHT_Y/2 == NS_CORNER_TOP_RIGHT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_BOTTOM_RIGHT_X/2 == NS_CORNER_BOTTOM_RIGHT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_BOTTOM_RIGHT_Y/2 == NS_CORNER_BOTTOM_RIGHT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_BOTTOM_LEFT_X/2 == NS_CORNER_BOTTOM_LEFT, "Check for Non Zero on side");
+ static_assert(NS_CORNER_BOTTOM_LEFT_Y/2 == NS_CORNER_BOTTOM_LEFT, "Check for Non Zero on side");
+
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ // corner is a "half corner" value, so dividing by two gives us a
+ // "full corner" value.
+ if (NonZeroStyleCoord(aCorners.Get(corner)) &&
+ IsCornerAdjacentToSide(corner/2, aSide))
+ return true;
+ }
+ return false;
+}
+
+/* static */ nsTransparencyMode
+nsLayoutUtils::GetFrameTransparency(nsIFrame* aBackgroundFrame,
+ nsIFrame* aCSSRootFrame) {
+ if (aCSSRootFrame->StyleEffects()->mOpacity < 1.0f)
+ return eTransparencyTransparent;
+
+ if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius))
+ return eTransparencyTransparent;
+
+ if (aCSSRootFrame->StyleDisplay()->mAppearance == NS_THEME_WIN_GLASS)
+ return eTransparencyGlass;
+
+ if (aCSSRootFrame->StyleDisplay()->mAppearance == NS_THEME_WIN_BORDERLESS_GLASS)
+ return eTransparencyBorderlessGlass;
+
+ nsITheme::Transparency transparency;
+ if (aCSSRootFrame->IsThemed(&transparency))
+ return transparency == nsITheme::eTransparent
+ ? eTransparencyTransparent
+ : eTransparencyOpaque;
+
+ // We need an uninitialized window to be treated as opaque because
+ // doing otherwise breaks window display effects on some platforms,
+ // specifically Vista. (bug 450322)
+ if (aBackgroundFrame->GetType() == nsGkAtoms::viewportFrame &&
+ !aBackgroundFrame->PrincipalChildList().FirstChild()) {
+ return eTransparencyOpaque;
+ }
+
+ nsStyleContext* bgSC;
+ if (!nsCSSRendering::FindBackground(aBackgroundFrame, &bgSC)) {
+ return eTransparencyTransparent;
+ }
+ const nsStyleBackground* bg = bgSC->StyleBackground();
+ if (NS_GET_A(bg->mBackgroundColor) < 255 ||
+ // bottom layer's clip is used for the color
+ bg->BottomLayer().mClip != NS_STYLE_IMAGELAYER_CLIP_BORDER)
+ return eTransparencyTransparent;
+ return eTransparencyOpaque;
+}
+
+static bool IsPopupFrame(nsIFrame* aFrame)
+{
+ // aFrame is a popup it's the list control frame dropdown for a combobox.
+ nsIAtom* frameType = aFrame->GetType();
+ if (frameType == nsGkAtoms::listControlFrame) {
+ nsListControlFrame* lcf = static_cast<nsListControlFrame*>(aFrame);
+ return lcf->IsInDropDownMode();
+ }
+
+ // ... or if it's a XUL menupopup frame.
+ return frameType == nsGkAtoms::menuPopupFrame;
+}
+
+/* static */ bool
+nsLayoutUtils::IsPopup(nsIFrame* aFrame)
+{
+ // Optimization: the frame can't possibly be a popup if it has no view.
+ if (!aFrame->HasView()) {
+ NS_ASSERTION(!IsPopupFrame(aFrame), "popup frame must have a view");
+ return false;
+ }
+ return IsPopupFrame(aFrame);
+}
+
+/* static */ nsIFrame*
+nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame)
+{
+ // We could use GetRootPresContext() here if the
+ // NS_FRAME_IN_POPUP frame bit is set.
+ nsIFrame* f = aFrame;
+ for (;;) {
+ if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ f = f->PresContext()->FrameManager()->GetRootFrame();
+ } else if (IsPopup(f)) {
+ return f;
+ }
+ nsIFrame* parent = GetCrossDocParentFrame(f);
+ if (!parent)
+ return f;
+ f = parent;
+ }
+}
+
+/* static */ nsIFrame*
+nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame)
+{
+ nsIFrame *f = aFrame;
+ for (;;) {
+ if (f->IsTransformed() || f->IsPreserve3DLeaf() || IsPopup(f)) {
+ return f;
+ }
+ nsIFrame* parent = GetCrossDocParentFrame(f);
+ if (!parent) {
+ return f;
+ }
+ f = parent;
+ }
+}
+
+/* static */ uint32_t
+nsLayoutUtils::GetTextRunFlagsForStyle(nsStyleContext* aStyleContext,
+ const nsStyleFont* aStyleFont,
+ const nsStyleText* aStyleText,
+ nscoord aLetterSpacing)
+{
+ uint32_t result = 0;
+ if (aLetterSpacing != 0) {
+ result |= gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES;
+ }
+ if (aStyleText->mControlCharacterVisibility == NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN) {
+ result |= gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS;
+ }
+ switch (aStyleContext->StyleText()->mTextRendering) {
+ case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED:
+ result |= gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
+ break;
+ case NS_STYLE_TEXT_RENDERING_AUTO:
+ if (aStyleFont->mFont.size <
+ aStyleContext->PresContext()->GetAutoQualityMinFontSize()) {
+ result |= gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
+ }
+ break;
+ default:
+ break;
+ }
+ return result | GetTextRunOrientFlagsForStyle(aStyleContext);
+}
+
+/* static */ uint32_t
+nsLayoutUtils::GetTextRunOrientFlagsForStyle(nsStyleContext* aStyleContext)
+{
+ uint8_t writingMode = aStyleContext->StyleVisibility()->mWritingMode;
+ switch (writingMode) {
+ case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
+ return gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL;
+
+ case NS_STYLE_WRITING_MODE_VERTICAL_LR:
+ case NS_STYLE_WRITING_MODE_VERTICAL_RL:
+ switch (aStyleContext->StyleVisibility()->mTextOrientation) {
+ case NS_STYLE_TEXT_ORIENTATION_MIXED:
+ return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED;
+ case NS_STYLE_TEXT_ORIENTATION_UPRIGHT:
+ return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS:
+ return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ default:
+ NS_NOTREACHED("unknown text-orientation");
+ return 0;
+ }
+
+ case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
+ return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
+
+ case NS_STYLE_WRITING_MODE_SIDEWAYS_RL:
+ return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+
+ default:
+ NS_NOTREACHED("unknown writing-mode");
+ return 0;
+ }
+}
+
+/* static */ void
+nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2,
+ nsRect* aHStrip, nsRect* aVStrip) {
+ NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
+ "expected rects at the same position");
+ nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
+ std::max(aR1.height, aR2.height));
+ nscoord VStripStart = std::min(aR1.width, aR2.width);
+ nscoord HStripStart = std::min(aR1.height, aR2.height);
+ *aVStrip = unionRect;
+ aVStrip->x += VStripStart;
+ aVStrip->width -= VStripStart;
+ *aHStrip = unionRect;
+ aHStrip->y += HStripStart;
+ aHStrip->height -= HStripStart;
+}
+
+nsDeviceContext*
+nsLayoutUtils::GetDeviceContextForScreenInfo(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ while (docShell) {
+ // Now make sure our size is up to date. That will mean that the device
+ // context does the right thing on multi-monitor systems when we return it to
+ // the caller. It will also make sure that our prescontext has been created,
+ // if we're supposed to have one.
+ nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
+ if (!win) {
+ // No reason to go on
+ return nullptr;
+ }
+
+ win->EnsureSizeUpToDate();
+
+ RefPtr<nsPresContext> presContext;
+ docShell->GetPresContext(getter_AddRefs(presContext));
+ if (presContext) {
+ nsDeviceContext* context = presContext->DeviceContext();
+ if (context) {
+ return context;
+ }
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ docShell->GetParent(getter_AddRefs(parentItem));
+ docShell = do_QueryInterface(parentItem);
+ }
+
+ return nullptr;
+}
+
+/* static */ bool
+nsLayoutUtils::IsReallyFixedPos(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame->GetParent(),
+ "IsReallyFixedPos called on frame not in tree");
+ NS_PRECONDITION(aFrame->StyleDisplay()->mPosition ==
+ NS_STYLE_POSITION_FIXED,
+ "IsReallyFixedPos called on non-'position:fixed' frame");
+
+ nsIAtom *parentType = aFrame->GetParent()->GetType();
+ return parentType == nsGkAtoms::viewportFrame ||
+ parentType == nsGkAtoms::pageContentFrame;
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ SurfaceFromElementResult result;
+
+ bool* isPremultiplied = nullptr;
+ if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
+ isPremultiplied = &result.mIsPremultiplied;
+ }
+
+ nsIntSize size = aOffscreenCanvas->GetWidthHeight();
+
+ result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(isPremultiplied);
+ if (!result.mSourceSurface) {
+ // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+ // draw nothing, so return an empty surface.
+ DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+ SurfaceFormat::B8G8R8A8);
+ if (dt) {
+ result.mSourceSurface = dt->Snapshot();
+ }
+ } else if (aTarget) {
+ RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+
+ result.mHasSize = true;
+ result.mSize = size;
+ result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly();
+
+ return result;
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ SurfaceFromElementResult result;
+ nsresult rv;
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ if (!imgRequest) {
+ // There's no image request. This is either because a request for
+ // a non-empty URI failed, or the URI is the empty string.
+ nsCOMPtr<nsIURI> currentURI;
+ aElement->GetCurrentURI(getter_AddRefs(currentURI));
+ if (!currentURI) {
+ // Treat the empty URI as available instead of broken state.
+ result.mHasSize = true;
+ }
+ return result;
+ }
+
+ uint32_t status;
+ imgRequest->GetImageStatus(&status);
+ result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE;
+ if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) {
+ // Spec says to use GetComplete, but that only works on
+ // nsIDOMHTMLImageElement, and we support all sorts of other stuff
+ // here. Do this for now pending spec clarification.
+ result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0;
+ return result;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ uint32_t noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS;
+
+ uint32_t whichFrame = (aSurfaceFlags & SFE_WANT_FIRST_FRAME)
+ ? (uint32_t) imgIContainer::FRAME_FIRST
+ : (uint32_t) imgIContainer::FRAME_CURRENT;
+ uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE;
+ if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
+ frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
+ if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
+ frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
+ result.mIsPremultiplied = false;
+ }
+
+ int32_t imgWidth, imgHeight;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+ HTMLImageElement* element = HTMLImageElement::FromContentOrNull(content);
+ if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR &&
+ element &&
+ imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ imgWidth = element->Width();
+ imgHeight = element->Height();
+ } else {
+ rv = imgContainer->GetWidth(&imgWidth);
+ nsresult rv2 = imgContainer->GetHeight(&imgHeight);
+ if (NS_FAILED(rv) || NS_FAILED(rv2))
+ return result;
+ }
+ result.mSize = IntSize(imgWidth, imgHeight);
+
+ if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ if (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) {
+ frameFlags |= imgIContainer::FLAG_WANT_DATA_SURFACE;
+ }
+ result.mSourceSurface = imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags);
+ if (!result.mSourceSurface) {
+ return result;
+ }
+ // The surface we return is likely to be cached. We don't want to have to
+ // convert to a surface that's compatible with aTarget each time it's used
+ // (that would result in terrible performance), so we convert once here
+ // upfront if aTarget is specified.
+ if (aTarget) {
+ RefPtr<SourceSurface> optSurface =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (optSurface) {
+ result.mSourceSurface = optSurface;
+ }
+ }
+ } else {
+ result.mDrawInfo.mImgContainer = imgContainer;
+ result.mDrawInfo.mWhichFrame = whichFrame;
+ result.mDrawInfo.mDrawingFlags = frameFlags;
+ }
+
+ int32_t corsmode;
+ if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
+ result.mCORSUsed = (corsmode != imgIRequest::CORS_NONE);
+ }
+
+ result.mPrincipal = principal.forget();
+ // no images, including SVG images, can load content from another domain.
+ result.mIsWriteOnly = false;
+ result.mImageRequest = imgRequest.forget();
+ return result;
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromElement(HTMLImageElement *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement),
+ aSurfaceFlags, aTarget);
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ SurfaceFromElementResult result;
+
+ bool* isPremultiplied = nullptr;
+ if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
+ isPremultiplied = &result.mIsPremultiplied;
+ }
+
+ IntSize size = aElement->GetSize();
+
+ result.mSourceSurface = aElement->GetSurfaceSnapshot(isPremultiplied);
+ if (!result.mSourceSurface) {
+ // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+ // draw nothing, so return an empty surface.
+ DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+ SurfaceFormat::B8G8R8A8);
+ if (dt) {
+ result.mSourceSurface = dt->Snapshot();
+ }
+ } else if (aTarget) {
+ RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+
+ // Ensure that any future changes to the canvas trigger proper invalidation,
+ // in case this is being used by -moz-element()
+ aElement->MarkContextClean();
+
+ result.mHasSize = true;
+ result.mSize = size;
+ result.mPrincipal = aElement->NodePrincipal();
+ result.mIsWriteOnly = aElement->IsWriteOnly();
+
+ return result;
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ SurfaceFromElementResult result;
+
+ NS_WARNING_ASSERTION(
+ (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) == 0,
+ "We can't support non-premultiplied alpha for video!");
+
+ if (aElement->ContainsRestrictedContent()) {
+ return result;
+ }
+
+ uint16_t readyState;
+ if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) &&
+ (readyState == nsIDOMHTMLMediaElement::HAVE_NOTHING ||
+ readyState == nsIDOMHTMLMediaElement::HAVE_METADATA)) {
+ result.mIsStillLoading = true;
+ return result;
+ }
+
+ // If it doesn't have a principal, just bail
+ nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
+ if (!principal)
+ return result;
+
+ ImageContainer* container = aElement->GetImageContainer();
+ if (!container)
+ return result;
+
+ AutoLockImage lockImage(container);
+
+ result.mLayersImage = lockImage.GetImage();
+ if (!result.mLayersImage)
+ return result;
+
+ if (aTarget) {
+ // They gave us a DrawTarget to optimize for, so even though we have a layers::Image,
+ // we should unconditionally grab a SourceSurface and try to optimize it.
+ result.mSourceSurface = result.mLayersImage->GetAsSourceSurface();
+ if (!result.mSourceSurface)
+ return result;
+
+ RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+
+ result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
+ result.mHasSize = true;
+ result.mSize = result.mLayersImage->GetSize();
+ result.mPrincipal = principal.forget();
+ result.mIsWriteOnly = false;
+
+ return result;
+}
+
+nsLayoutUtils::SurfaceFromElementResult
+nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget)
+{
+ // If it's a <canvas>, we may be able to just grab its internal surface
+ if (HTMLCanvasElement* canvas =
+ HTMLCanvasElement::FromContentOrNull(aElement)) {
+ return SurfaceFromElement(canvas, aSurfaceFlags, aTarget);
+ }
+
+ // Maybe it's <video>?
+ if (HTMLVideoElement* video =
+ HTMLVideoElement::FromContentOrNull(aElement)) {
+ return SurfaceFromElement(video, aSurfaceFlags, aTarget);
+ }
+
+ // Finally, check if it's a normal image
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
+
+ if (!imageLoader) {
+ return SurfaceFromElementResult();
+ }
+
+ return SurfaceFromElement(imageLoader, aSurfaceFlags, aTarget);
+}
+
+/* static */
+nsIContent*
+nsLayoutUtils::GetEditableRootContentByContentEditable(nsIDocument* aDocument)
+{
+ // If the document is in designMode we should return nullptr.
+ if (!aDocument || aDocument->HasFlag(NODE_IS_EDITABLE)) {
+ return nullptr;
+ }
+
+ // contenteditable only works with HTML document.
+ // Note: Use nsIDOMHTMLDocument rather than nsIHTMLDocument for getting the
+ // body node because nsIDOMHTMLDocument::GetBody() does something
+ // additional work for some cases and EditorBase uses them.
+ nsCOMPtr<nsIDOMHTMLDocument> domHTMLDoc = do_QueryInterface(aDocument);
+ if (!domHTMLDoc) {
+ return nullptr;
+ }
+
+ Element* rootElement = aDocument->GetRootElement();
+ if (rootElement && rootElement->IsEditable()) {
+ return rootElement;
+ }
+
+ // If there are no editable root element, check its <body> element.
+ // Note that the body element could be <frameset> element.
+ nsCOMPtr<nsIDOMHTMLElement> body;
+ nsresult rv = domHTMLDoc->GetBody(getter_AddRefs(body));
+ nsCOMPtr<nsIContent> content = do_QueryInterface(body);
+ if (NS_SUCCEEDED(rv) && content && content->IsEditable()) {
+ return content;
+ }
+ return nullptr;
+}
+
+#ifdef DEBUG
+/* static */ void
+nsLayoutUtils::AssertNoDuplicateContinuations(nsIFrame* aContainer,
+ const nsFrameList& aFrameList)
+{
+ for (nsIFrame* f : aFrameList) {
+ // Check only later continuations of f; we deal with checking the
+ // earlier continuations when we hit those earlier continuations in
+ // the frame list.
+ for (nsIFrame *c = f; (c = c->GetNextInFlow());) {
+ NS_ASSERTION(c->GetParent() != aContainer ||
+ !aFrameList.ContainsFrame(c),
+ "Two continuations of the same frame in the same "
+ "frame list");
+ }
+ }
+}
+
+// Is one of aFrame's ancestors a letter frame?
+static bool
+IsInLetterFrame(nsIFrame *aFrame)
+{
+ for (nsIFrame *f = aFrame->GetParent(); f; f = f->GetParent()) {
+ if (f->GetType() == nsGkAtoms::letterFrame) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ void
+nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(nsIFrame *aSubtreeRoot)
+{
+ NS_ASSERTION(aSubtreeRoot->GetPrevInFlow(),
+ "frame tree not empty, but caller reported complete status");
+
+ // Also assert that text frames map no text.
+ int32_t start, end;
+ nsresult rv = aSubtreeRoot->GetOffsets(start, end);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOffsets failed");
+ // In some cases involving :first-letter, we'll partially unlink a
+ // continuation in the middle of a continuation chain from its
+ // previous and next continuations before destroying it, presumably so
+ // that we don't also destroy the later continuations. Once we've
+ // done this, GetOffsets returns incorrect values.
+ // For examples, see list of tests in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=619021#c29
+ NS_ASSERTION(start == end || IsInLetterFrame(aSubtreeRoot),
+ "frame tree not empty, but caller reported complete status");
+
+ nsIFrame::ChildListIterator lists(aSubtreeRoot);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(childFrames.get());
+ }
+ }
+}
+#endif
+
+static void
+GetFontFacesForFramesInner(nsIFrame* aFrame, nsFontFaceList* aFontFaceList)
+{
+ NS_PRECONDITION(aFrame, "NULL frame pointer");
+
+ if (aFrame->GetType() == nsGkAtoms::textFrame) {
+ if (!aFrame->GetPrevContinuation()) {
+ nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true,
+ aFontFaceList);
+ }
+ return;
+ }
+
+ nsIFrame::ChildListID childLists[] = { nsIFrame::kPrincipalList,
+ nsIFrame::kPopupList };
+ for (size_t i = 0; i < ArrayLength(childLists); ++i) {
+ nsFrameList children(aFrame->GetChildList(childLists[i]));
+ for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
+ nsIFrame* child = e.get();
+ child = nsPlaceholderFrame::GetRealFrameFor(child);
+ GetFontFacesForFramesInner(child, aFontFaceList);
+ }
+ }
+}
+
+/* static */
+nsresult
+nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
+ nsFontFaceList* aFontFaceList)
+{
+ NS_PRECONDITION(aFrame, "NULL frame pointer");
+
+ while (aFrame) {
+ GetFontFacesForFramesInner(aFrame, aFontFaceList);
+ aFrame = GetNextContinuationOrIBSplitSibling(aFrame);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
+ int32_t aStartOffset, int32_t aEndOffset,
+ bool aFollowContinuations,
+ nsFontFaceList* aFontFaceList)
+{
+ NS_PRECONDITION(aFrame, "NULL frame pointer");
+
+ if (aFrame->GetType() != nsGkAtoms::textFrame) {
+ return NS_OK;
+ }
+
+ nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
+ do {
+ int32_t fstart = std::max(curr->GetContentOffset(), aStartOffset);
+ int32_t fend = std::min(curr->GetContentEnd(), aEndOffset);
+ if (fstart >= fend) {
+ curr = static_cast<nsTextFrame*>(curr->GetNextContinuation());
+ continue;
+ }
+
+ // curr is overlapping with the offset we want
+ gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
+ NS_ENSURE_TRUE(textRun, NS_ERROR_OUT_OF_MEMORY);
+
+ // include continuations in the range that share the same textrun
+ nsTextFrame* next = nullptr;
+ if (aFollowContinuations && fend < aEndOffset) {
+ next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
+ while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
+ fend = std::min(next->GetContentEnd(), aEndOffset);
+ next = fend < aEndOffset ?
+ static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr;
+ }
+ }
+
+ uint32_t skipStart = iter.ConvertOriginalToSkipped(fstart);
+ uint32_t skipEnd = iter.ConvertOriginalToSkipped(fend);
+ aFontFaceList->AddFontsFromTextRun(textRun, skipStart, skipEnd - skipStart);
+ curr = next;
+ } while (aFollowContinuations && curr);
+
+ return NS_OK;
+}
+
+/* static */
+size_t
+nsLayoutUtils::SizeOfTextRunsForFrames(nsIFrame* aFrame,
+ MallocSizeOf aMallocSizeOf,
+ bool clear)
+{
+ NS_PRECONDITION(aFrame, "NULL frame pointer");
+
+ size_t total = 0;
+
+ if (aFrame->GetType() == nsGkAtoms::textFrame) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
+ for (uint32_t i = 0; i < 2; ++i) {
+ gfxTextRun *run = textFrame->GetTextRun(
+ (i != 0) ? nsTextFrame::eInflated : nsTextFrame::eNotInflated);
+ if (run) {
+ if (clear) {
+ run->ResetSizeOfAccountingFlags();
+ } else {
+ total += run->MaybeSizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ }
+ return total;
+ }
+
+ AutoTArray<nsIFrame::ChildList,4> childListArray;
+ aFrame->GetChildLists(&childListArray);
+
+ for (nsIFrame::ChildListArrayIterator childLists(childListArray);
+ !childLists.IsDone(); childLists.Next()) {
+ for (nsFrameList::Enumerator e(childLists.CurrentList());
+ !e.AtEnd(); e.Next()) {
+ total += SizeOfTextRunsForFrames(e.get(), aMallocSizeOf, clear);
+ }
+ }
+ return total;
+}
+
+struct PrefCallbacks
+{
+ const char* name;
+ PrefChangedFunc func;
+};
+static const PrefCallbacks kPrefCallbacks[] = {
+ { GRID_ENABLED_PREF_NAME,
+ GridEnabledPrefChangeCallback },
+ { WEBKIT_PREFIXES_ENABLED_PREF_NAME,
+ WebkitPrefixEnabledPrefChangeCallback },
+ { TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME,
+ TextAlignUnsafeEnabledPrefChangeCallback },
+ { DISPLAY_CONTENTS_ENABLED_PREF_NAME,
+ DisplayContentsEnabledPrefChangeCallback },
+ { FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME,
+ FloatLogicalValuesEnabledPrefChangeCallback },
+ { BG_CLIP_TEXT_ENABLED_PREF_NAME,
+ BackgroundClipTextEnabledPrefChangeCallback },
+};
+
+/* static */
+void
+nsLayoutUtils::Initialize()
+{
+ Preferences::AddUintVarCache(&sFontSizeInflationMaxRatio,
+ "font.size.inflation.maxRatio");
+ Preferences::AddUintVarCache(&sFontSizeInflationEmPerLine,
+ "font.size.inflation.emPerLine");
+ Preferences::AddUintVarCache(&sFontSizeInflationMinTwips,
+ "font.size.inflation.minTwips");
+ Preferences::AddUintVarCache(&sFontSizeInflationLineThreshold,
+ "font.size.inflation.lineThreshold");
+ Preferences::AddIntVarCache(&sFontSizeInflationMappingIntercept,
+ "font.size.inflation.mappingIntercept");
+ Preferences::AddBoolVarCache(&sFontSizeInflationForceEnabled,
+ "font.size.inflation.forceEnabled");
+ Preferences::AddBoolVarCache(&sFontSizeInflationDisabledInMasterProcess,
+ "font.size.inflation.disabledInMasterProcess");
+ Preferences::AddBoolVarCache(&sInvalidationDebuggingIsEnabled,
+ "nglayout.debug.invalidation");
+ Preferences::AddBoolVarCache(&sCSSVariablesEnabled,
+ "layout.css.variables.enabled");
+ Preferences::AddBoolVarCache(&sInterruptibleReflowEnabled,
+ "layout.interruptible-reflow.enabled");
+ Preferences::AddBoolVarCache(&sSVGTransformBoxEnabled,
+ "svg.transform-box.enabled");
+ Preferences::AddBoolVarCache(&sTextCombineUprightDigitsEnabled,
+ "layout.css.text-combine-upright-digits.enabled");
+#ifdef MOZ_STYLO
+ Preferences::AddBoolVarCache(&sStyloEnabled,
+ "layout.css.servo.enabled");
+#endif
+ Preferences::AddUintVarCache(&sIdlePeriodDeadlineLimit,
+ "layout.idle_period.time_limit",
+ DEFAULT_IDLE_PERIOD_TIME_LIMIT);
+ Preferences::AddUintVarCache(&sQuiescentFramesBeforeIdlePeriod,
+ "layout.idle_period.required_quiescent_frames",
+ DEFAULT_QUIESCENT_FRAMES);
+
+ for (auto& callback : kPrefCallbacks) {
+ Preferences::RegisterCallbackAndCall(callback.func, callback.name);
+ }
+ nsComputedDOMStyle::RegisterPrefChangeCallbacks();
+}
+
+/* static */
+void
+nsLayoutUtils::Shutdown()
+{
+ if (sContentMap) {
+ delete sContentMap;
+ sContentMap = nullptr;
+ }
+
+ for (auto& callback : kPrefCallbacks) {
+ Preferences::UnregisterCallback(callback.func, callback.name);
+ }
+ nsComputedDOMStyle::UnregisterPrefChangeCallbacks();
+
+ // so the cached initial quotes array doesn't appear to be a leak
+ nsStyleList::Shutdown();
+}
+
+/* static */
+void
+nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered)
+{
+ if (!aPresContext) {
+ return;
+ }
+
+ if (aRequestRegistered && *aRequestRegistered) {
+ // Our request is already registered with the refresh driver, so
+ // no need to register it again.
+ return;
+ }
+
+ if (aRequest) {
+ if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) {
+ NS_WARNING("Unable to add image request");
+ return;
+ }
+
+ if (aRequestRegistered) {
+ *aRequestRegistered = true;
+ }
+ }
+}
+
+/* static */
+void
+nsLayoutUtils::RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered)
+{
+ if (!aPresContext) {
+ return;
+ }
+
+ if (aRequestRegistered && *aRequestRegistered) {
+ // Our request is already registered with the refresh driver, so
+ // no need to register it again.
+ return;
+ }
+
+ if (aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ // Check to verify that the image is animated. If so, then add it to the
+ // list of images tracked by the refresh driver.
+ bool isAnimated = false;
+ nsresult rv = image->GetAnimated(&isAnimated);
+ if (NS_SUCCEEDED(rv) && isAnimated) {
+ if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) {
+ NS_WARNING("Unable to add image request");
+ return;
+ }
+
+ if (aRequestRegistered) {
+ *aRequestRegistered = true;
+ }
+ }
+ }
+ }
+}
+
+/* static */
+void
+nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered)
+{
+ if (!aPresContext) {
+ return;
+ }
+
+ // Deregister our imgIRequest with the refresh driver to
+ // complete tear-down, but only if it has been registered
+ if (aRequestRegistered && !*aRequestRegistered) {
+ return;
+ }
+
+ if (aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
+
+ if (aRequestRegistered) {
+ *aRequestRegistered = false;
+ }
+ }
+ }
+}
+
+/* static */
+void
+nsLayoutUtils::PostRestyleEvent(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint)
+{
+ nsIDocument* doc = aElement->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (presShell) {
+ presShell->GetPresContext()->RestyleManager()->PostRestyleEvent(
+ aElement, aRestyleHint, aMinChangeHint);
+ }
+ }
+}
+
+nsSetAttrRunnable::nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
+ const nsAString& aValue)
+ : mContent(aContent),
+ mAttrName(aAttrName),
+ mValue(aValue)
+{
+ NS_ASSERTION(aContent && aAttrName, "Missing stuff, prepare to crash");
+}
+
+nsSetAttrRunnable::nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
+ int32_t aValue)
+ : mContent(aContent),
+ mAttrName(aAttrName)
+{
+ NS_ASSERTION(aContent && aAttrName, "Missing stuff, prepare to crash");
+ mValue.AppendInt(aValue);
+}
+
+NS_IMETHODIMP
+nsSetAttrRunnable::Run()
+{
+ return mContent->SetAttr(kNameSpaceID_None, mAttrName, mValue, true);
+}
+
+nsUnsetAttrRunnable::nsUnsetAttrRunnable(nsIContent* aContent,
+ nsIAtom* aAttrName)
+ : mContent(aContent),
+ mAttrName(aAttrName)
+{
+ NS_ASSERTION(aContent && aAttrName, "Missing stuff, prepare to crash");
+}
+
+NS_IMETHODIMP
+nsUnsetAttrRunnable::Run()
+{
+ return mContent->UnsetAttr(kNameSpaceID_None, mAttrName, true);
+}
+
+/**
+ * Compute the minimum font size inside of a container with the given
+ * width, such that **when the user zooms the container to fill the full
+ * width of the device**, the fonts satisfy our minima.
+ */
+static nscoord
+MinimumFontSizeFor(nsPresContext* aPresContext, WritingMode aWritingMode,
+ nscoord aContainerISize)
+{
+ nsIPresShell* presShell = aPresContext->PresShell();
+
+ uint32_t emPerLine = presShell->FontSizeInflationEmPerLine();
+ uint32_t minTwips = presShell->FontSizeInflationMinTwips();
+ if (emPerLine == 0 && minTwips == 0) {
+ return 0;
+ }
+
+ // Clamp the container width to the device dimensions
+ nscoord iFrameISize = aWritingMode.IsVertical()
+ ? aPresContext->GetVisibleArea().height
+ : aPresContext->GetVisibleArea().width;
+ nscoord effectiveContainerISize = std::min(iFrameISize, aContainerISize);
+
+ nscoord byLine = 0, byInch = 0;
+ if (emPerLine != 0) {
+ byLine = effectiveContainerISize / emPerLine;
+ }
+ if (minTwips != 0) {
+ // REVIEW: Is this giving us app units and sizes *not* counting
+ // viewport scaling?
+ gfxSize screenSize = aPresContext->ScreenSizeInchesForFontInflation();
+ float deviceISizeInches = aWritingMode.IsVertical()
+ ? screenSize.height : screenSize.width;
+ byInch = NSToCoordRound(effectiveContainerISize /
+ (deviceISizeInches * 1440 /
+ minTwips ));
+ }
+ return std::max(byLine, byInch);
+}
+
+/* static */ float
+nsLayoutUtils::FontSizeInflationInner(const nsIFrame *aFrame,
+ nscoord aMinFontSize)
+{
+ // Note that line heights should be inflated by the same ratio as the
+ // font size of the same text; thus we operate only on the font size
+ // even when we're scaling a line height.
+ nscoord styleFontSize = aFrame->StyleFont()->mFont.size;
+ if (styleFontSize <= 0) {
+ // Never scale zero font size.
+ return 1.0;
+ }
+
+ if (aMinFontSize <= 0) {
+ // No need to scale.
+ return 1.0;
+ }
+
+ // If between this current frame and its font inflation container there is a
+ // non-inline element with fixed width or height, then we should not inflate
+ // fonts for this frame.
+ for (const nsIFrame* f = aFrame;
+ f && !f->IsContainerForFontSizeInflation();
+ f = f->GetParent()) {
+ nsIContent* content = f->GetContent();
+ nsIAtom* fType = f->GetType();
+ nsIFrame* parent = f->GetParent();
+ // Also, if there is more than one frame corresponding to a single
+ // content node, we want the outermost one.
+ if (!(parent && parent->GetContent() == content) &&
+ // ignore width/height on inlines since they don't apply
+ fType != nsGkAtoms::inlineFrame &&
+ // ignore width on radios and checkboxes since we enlarge them and
+ // they have width/height in ua.css
+ fType != nsGkAtoms::formControlFrame) {
+ // ruby annotations should have the same inflation as its
+ // grandparent, which is the ruby frame contains the annotation.
+ if (fType == nsGkAtoms::rubyTextFrame) {
+ MOZ_ASSERT(parent &&
+ parent->GetType() == nsGkAtoms::rubyTextContainerFrame);
+ nsIFrame* grandparent = parent->GetParent();
+ MOZ_ASSERT(grandparent &&
+ grandparent->GetType() == nsGkAtoms::rubyFrame);
+ return FontSizeInflationFor(grandparent);
+ }
+ nsStyleCoord stylePosWidth = f->StylePosition()->mWidth;
+ nsStyleCoord stylePosHeight = f->StylePosition()->mHeight;
+ if (stylePosWidth.GetUnit() != eStyleUnit_Auto ||
+ stylePosHeight.GetUnit() != eStyleUnit_Auto) {
+
+ return 1.0;
+ }
+ }
+ }
+
+ int32_t interceptParam = nsLayoutUtils::FontSizeInflationMappingIntercept();
+ float maxRatio = (float)nsLayoutUtils::FontSizeInflationMaxRatio() / 100.0f;
+
+ float ratio = float(styleFontSize) / float(aMinFontSize);
+ float inflationRatio;
+
+ // Given a minimum inflated font size m, a specified font size s, we want to
+ // find the inflated font size i and then return the ratio of i to s (i/s).
+ if (interceptParam >= 0) {
+ // Since the mapping intercept parameter P is greater than zero, we use it
+ // to determine the point where our mapping function intersects the i=s
+ // line. This means that we have an equation of the form:
+ //
+ // i = m + s·(P/2)/(1 + P/2), if s <= (1 + P/2)·m
+ // i = s, if s >= (1 + P/2)·m
+
+ float intercept = 1 + float(interceptParam)/2.0f;
+ if (ratio >= intercept) {
+ // If we're already at 1+P/2 or more times the minimum, don't scale.
+ return 1.0;
+ }
+
+ // The point (intercept, intercept) is where the part of the i vs. s graph
+ // that's not slope 1 meets the i=s line. (This part of the
+ // graph is a line from (0, m), to that point). We calculate the
+ // intersection point to be ((1+P/2)m, (1+P/2)m), where P is the
+ // intercept parameter above. We then need to return i/s.
+ inflationRatio = (1.0f + (ratio * (intercept - 1) / intercept)) / ratio;
+ } else {
+ // This is the case where P is negative. We essentially want to implement
+ // the case for P=infinity here, so we make i = s + m, which means that
+ // i/s = s/s + m/s = 1 + 1/ratio
+ inflationRatio = 1 + 1.0f / ratio;
+ }
+
+ if (maxRatio > 1.0 && inflationRatio > maxRatio) {
+ return maxRatio;
+ } else {
+ return inflationRatio;
+ }
+}
+
+static bool
+ShouldInflateFontsForContainer(const nsIFrame *aFrame)
+{
+ // We only want to inflate fonts for text that is in a place
+ // with room to expand. The question is what the best heuristic for
+ // that is...
+ // For now, we're going to use NS_FRAME_IN_CONSTRAINED_BSIZE, which
+ // indicates whether the frame is inside something with a constrained
+ // block-size (propagating down the tree), but the propagation stops when
+ // we hit overflow-y [or -x, for vertical mode]: scroll or auto.
+ const nsStyleText* styleText = aFrame->StyleText();
+
+ return styleText->mTextSizeAdjust != NS_STYLE_TEXT_SIZE_ADJUST_NONE &&
+ !(aFrame->GetStateBits() & NS_FRAME_IN_CONSTRAINED_BSIZE) &&
+ // We also want to disable font inflation for containers that have
+ // preformatted text.
+ // MathML cells need special treatment. See bug 1002526 comment 56.
+ (styleText->WhiteSpaceCanWrap(aFrame) ||
+ aFrame->IsFrameOfType(nsIFrame::eMathML));
+}
+
+nscoord
+nsLayoutUtils::InflationMinFontSizeFor(const nsIFrame *aFrame)
+{
+ nsPresContext *presContext = aFrame->PresContext();
+ if (!FontSizeInflationEnabled(presContext) ||
+ presContext->mInflationDisabledForShrinkWrap) {
+ return 0;
+ }
+
+ for (const nsIFrame *f = aFrame; f; f = f->GetParent()) {
+ if (f->IsContainerForFontSizeInflation()) {
+ if (!ShouldInflateFontsForContainer(f)) {
+ return 0;
+ }
+
+ nsFontInflationData *data =
+ nsFontInflationData::FindFontInflationDataFor(aFrame);
+ // FIXME: The need to null-check here is sort of a bug, and might
+ // lead to incorrect results.
+ if (!data || !data->InflationEnabled()) {
+ return 0;
+ }
+
+ return MinimumFontSizeFor(aFrame->PresContext(),
+ aFrame->GetWritingMode(),
+ data->EffectiveISize());
+ }
+ }
+
+ MOZ_ASSERT(false, "root should always be container");
+
+ return 0;
+}
+
+float
+nsLayoutUtils::FontSizeInflationFor(const nsIFrame *aFrame)
+{
+ if (aFrame->IsSVGText()) {
+ const nsIFrame* container = aFrame;
+ while (container->GetType() != nsGkAtoms::svgTextFrame) {
+ container = container->GetParent();
+ }
+ NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
+ return
+ static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
+ }
+
+ if (!FontSizeInflationEnabled(aFrame->PresContext())) {
+ return 1.0f;
+ }
+
+ return FontSizeInflationInner(aFrame, InflationMinFontSizeFor(aFrame));
+}
+
+/* static */ bool
+nsLayoutUtils::FontSizeInflationEnabled(nsPresContext *aPresContext)
+{
+ nsIPresShell* presShell = aPresContext->GetPresShell();
+
+ if (!presShell) {
+ return false;
+ }
+
+ return presShell->FontSizeInflationEnabled();
+}
+
+/* static */ nsRect
+nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame,
+ const nsSize& aFrameSize)
+{
+ nsCSSShadowArray* boxShadows = aFrame->StyleEffects()->mBoxShadow;
+ if (!boxShadows) {
+ return nsRect();
+ }
+
+ bool nativeTheme;
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ nsITheme::Transparency transparency;
+ if (aFrame->IsThemed(styleDisplay, &transparency)) {
+ // For opaque (rectangular) theme widgets we can take the generic
+ // border-box path with border-radius disabled.
+ nativeTheme = transparency != nsITheme::eOpaque;
+ } else {
+ nativeTheme = false;
+ }
+
+ nsRect frameRect = nativeTheme ?
+ aFrame->GetVisualOverflowRectRelativeToSelf() :
+ nsRect(nsPoint(0, 0), aFrameSize);
+
+ nsRect shadows;
+ int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
+ for (uint32_t i = 0; i < boxShadows->Length(); ++i) {
+ nsRect tmpRect = frameRect;
+ nsCSSShadowItem* shadow = boxShadows->ShadowAt(i);
+
+ // inset shadows are never painted outside the frame
+ if (shadow->mInset)
+ continue;
+
+ tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
+ tmpRect.Inflate(shadow->mSpread);
+ tmpRect.Inflate(
+ nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D));
+ shadows.UnionRect(shadows, tmpRect);
+ }
+ return shadows;
+}
+
+/* static */ bool
+nsLayoutUtils::GetContentViewerSize(nsPresContext* aPresContext,
+ LayoutDeviceIntSize& aOutSize)
+{
+ nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ if (!cv) {
+ return false;
+ }
+
+ nsIntRect bounds;
+ cv->GetBounds(bounds);
+ aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
+ return true;
+}
+
+static bool
+UpdateCompositionBoundsForRCDRSF(ParentLayerRect& aCompBounds,
+ nsPresContext* aPresContext,
+ bool aScaleContentViewerSize)
+{
+ nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
+ if (!rootFrame) {
+ return false;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
+ nsIWidget* widget = rootFrame->GetNearestWidget();
+#else
+ nsView* view = rootFrame->GetView();
+ nsIWidget* widget = view ? view->GetWidget() : nullptr;
+#endif
+
+ if (widget) {
+ LayoutDeviceIntRect widgetBounds = widget->GetBounds();
+ widgetBounds.MoveTo(0, 0);
+ aCompBounds = ParentLayerRect(
+ ViewAs<ParentLayerPixel>(
+ widgetBounds,
+ PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF));
+ return true;
+ }
+
+ LayoutDeviceIntSize contentSize;
+ if (nsLayoutUtils::GetContentViewerSize(aPresContext, contentSize)) {
+ LayoutDeviceToParentLayerScale scale;
+ if (aScaleContentViewerSize && aPresContext->GetParentPresContext()) {
+ scale = LayoutDeviceToParentLayerScale(
+ aPresContext->GetParentPresContext()->PresShell()->GetCumulativeResolution());
+ }
+ aCompBounds.SizeTo(contentSize * scale);
+ return true;
+ }
+
+ return false;
+}
+
+/* static */ nsMargin
+nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(nsIFrame* aScrollFrame)
+{
+ if (!aScrollFrame || !aScrollFrame->GetScrollTargetFrame()) {
+ return nsMargin();
+ }
+ nsPresContext* presContext = aScrollFrame->PresContext();
+ nsIPresShell* presShell = presContext->GetPresShell();
+ if (!presShell) {
+ return nsMargin();
+ }
+ bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
+ bool isRootContentDocRootScrollFrame = isRootScrollFrame
+ && presContext->IsRootContentDocument();
+ if (!isRootContentDocRootScrollFrame) {
+ return nsMargin();
+ }
+ if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars)) {
+ return nsMargin();
+ }
+ nsIScrollableFrame* scrollableFrame = aScrollFrame->GetScrollTargetFrame();
+ if (!scrollableFrame) {
+ return nsMargin();
+ }
+ return scrollableFrame->GetActualScrollbarSizes();
+}
+
+/* static */ nsSize
+nsLayoutUtils::CalculateCompositionSizeForFrame(nsIFrame* aFrame, bool aSubtractScrollbars)
+{
+ // If we have a scrollable frame, restrict the composition bounds to its
+ // scroll port. The scroll port excludes the frame borders and the scroll
+ // bars, which we don't want to be part of the composition bounds.
+ nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame();
+ nsRect rect = scrollableFrame ? scrollableFrame->GetScrollPortRect() : aFrame->GetRect();
+ nsSize size = rect.Size();
+
+ nsPresContext* presContext = aFrame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+
+ bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument()
+ && aFrame == presShell->GetRootScrollFrame();
+ if (isRootContentDocRootScrollFrame) {
+ ParentLayerRect compBounds;
+ if (UpdateCompositionBoundsForRCDRSF(compBounds, presContext, false)) {
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+ size = nsSize(compBounds.width * auPerDevPixel, compBounds.height * auPerDevPixel);
+ }
+ }
+
+ if (aSubtractScrollbars) {
+ nsMargin margins = ScrollbarAreaToExcludeFromCompositionBoundsFor(aFrame);
+ size.width -= margins.LeftRight();
+ size.height -= margins.TopBottom();
+ }
+
+ return size;
+}
+
+/* static */ CSSSize
+nsLayoutUtils::CalculateRootCompositionSize(nsIFrame* aFrame,
+ bool aIsRootContentDocRootScrollFrame,
+ const FrameMetrics& aMetrics)
+{
+
+ if (aIsRootContentDocRootScrollFrame) {
+ return ViewAs<LayerPixel>(aMetrics.GetCompositionBounds().Size(),
+ PixelCastJustification::ParentLayerToLayerForRootComposition)
+ * LayerToScreenScale(1.0f)
+ / aMetrics.DisplayportPixelsPerCSSPixel();
+ }
+ nsPresContext* presContext = aFrame->PresContext();
+ ScreenSize rootCompositionSize;
+ nsPresContext* rootPresContext =
+ presContext->GetToplevelContentDocumentPresContext();
+ if (!rootPresContext) {
+ rootPresContext = presContext->GetRootPresContext();
+ }
+ nsIPresShell* rootPresShell = nullptr;
+ if (rootPresContext) {
+ rootPresShell = rootPresContext->PresShell();
+ if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) {
+ LayoutDeviceToLayerScale2D cumulativeResolution(
+ rootPresShell->GetCumulativeResolution()
+ * nsLayoutUtils::GetTransformToAncestorScale(rootFrame));
+ ParentLayerRect compBounds;
+ if (UpdateCompositionBoundsForRCDRSF(compBounds, rootPresContext, true)) {
+ rootCompositionSize = ViewAs<ScreenPixel>(compBounds.Size(),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+ } else {
+ int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel();
+ LayerSize frameSize =
+ (LayoutDeviceRect::FromAppUnits(rootFrame->GetRect(), rootAUPerDevPixel)
+ * cumulativeResolution).Size();
+ rootCompositionSize = frameSize * LayerToScreenScale(1.0f);
+ }
+ }
+ } else {
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ LayoutDeviceIntRect widgetBounds = widget->GetBounds();
+ rootCompositionSize = ScreenSize(
+ ViewAs<ScreenPixel>(widgetBounds.Size(),
+ PixelCastJustification::LayoutDeviceIsScreenForBounds));
+ }
+
+ // Adjust composition size for the size of scroll bars.
+ nsIFrame* rootRootScrollFrame = rootPresShell ? rootPresShell->GetRootScrollFrame() : nullptr;
+ nsMargin scrollbarMargins = ScrollbarAreaToExcludeFromCompositionBoundsFor(rootRootScrollFrame);
+ LayoutDeviceMargin margins = LayoutDeviceMargin::FromAppUnits(scrollbarMargins,
+ rootPresContext->AppUnitsPerDevPixel());
+ // Scrollbars are not subject to resolution scaling, so LD pixels = layer pixels for them.
+ rootCompositionSize.width -= margins.LeftRight();
+ rootCompositionSize.height -= margins.TopBottom();
+
+ return rootCompositionSize / aMetrics.DisplayportPixelsPerCSSPixel();
+}
+
+/* static */ nsRect
+nsLayoutUtils::CalculateScrollableRectForFrame(nsIScrollableFrame* aScrollableFrame, nsIFrame* aRootFrame)
+{
+ nsRect contentBounds;
+ if (aScrollableFrame) {
+ contentBounds = aScrollableFrame->GetScrollRange();
+
+ nsPoint scrollPosition = aScrollableFrame->GetScrollPosition();
+ if (aScrollableFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
+ contentBounds.y = scrollPosition.y;
+ contentBounds.height = 0;
+ }
+ if (aScrollableFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
+ contentBounds.x = scrollPosition.x;
+ contentBounds.width = 0;
+ }
+
+ contentBounds.width += aScrollableFrame->GetScrollPortRect().width;
+ contentBounds.height += aScrollableFrame->GetScrollPortRect().height;
+ } else {
+ contentBounds = aRootFrame->GetRect();
+ }
+ return contentBounds;
+}
+
+/* static */ nsRect
+nsLayoutUtils::CalculateExpandedScrollableRect(nsIFrame* aFrame)
+{
+ nsRect scrollableRect =
+ CalculateScrollableRectForFrame(aFrame->GetScrollTargetFrame(),
+ aFrame->PresContext()->PresShell()->GetRootFrame());
+ nsSize compSize = CalculateCompositionSizeForFrame(aFrame);
+
+ if (aFrame == aFrame->PresContext()->PresShell()->GetRootScrollFrame()) {
+ // the composition size for the root scroll frame does not include the
+ // local resolution, so we adjust.
+ float res = aFrame->PresContext()->PresShell()->GetResolution();
+ compSize.width = NSToCoordRound(compSize.width / res);
+ compSize.height = NSToCoordRound(compSize.height / res);
+ }
+
+ if (scrollableRect.width < compSize.width) {
+ scrollableRect.x = std::max(0,
+ scrollableRect.x - (compSize.width - scrollableRect.width));
+ scrollableRect.width = compSize.width;
+ }
+
+ if (scrollableRect.height < compSize.height) {
+ scrollableRect.y = std::max(0,
+ scrollableRect.y - (compSize.height - scrollableRect.height));
+ scrollableRect.height = compSize.height;
+ }
+ return scrollableRect;
+}
+
+/* static */ void
+nsLayoutUtils::DoLogTestDataForPaint(LayerManager* aManager,
+ ViewID aScrollId,
+ const std::string& aKey,
+ const std::string& aValue)
+{
+ if (ClientLayerManager* mgr = aManager->AsClientLayerManager()) {
+ mgr->LogTestDataForCurrentPaint(aScrollId, aKey, aValue);
+ }
+}
+
+/* static */ bool
+nsLayoutUtils::IsAPZTestLoggingEnabled()
+{
+ return gfxPrefs::APZTestLoggingEnabled();
+}
+
+////////////////////////////////////////
+// SurfaceFromElementResult
+
+nsLayoutUtils::SurfaceFromElementResult::SurfaceFromElementResult()
+ // Use safe default values here
+ : mIsWriteOnly(true)
+ , mIsStillLoading(false)
+ , mHasSize(false)
+ , mCORSUsed(false)
+ , mIsPremultiplied(true)
+{
+}
+
+const RefPtr<mozilla::gfx::SourceSurface>&
+nsLayoutUtils::SurfaceFromElementResult::GetSourceSurface()
+{
+ if (!mSourceSurface && mLayersImage) {
+ mSourceSurface = mLayersImage->GetAsSourceSurface();
+ }
+
+ return mSourceSurface;
+}
+
+////////////////////////////////////////
+
+bool
+nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame)
+{
+ return GetAsBlock(aFrame) && !aFrame->IsBlockWrapper();
+}
+
+bool
+nsLayoutUtils::NeedsPrintPreviewBackground(nsPresContext* aPresContext)
+{
+ return aPresContext->IsRootPaginatedDocument() &&
+ (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
+ aPresContext->Type() == nsPresContext::eContext_PageLayout);
+}
+
+AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame *aFrame)
+{
+ // FIXME: Now that inflation calculations are based on the flow
+ // root's NCA's (nearest common ancestor of its inflatable
+ // descendants) width, we could probably disable inflation in
+ // fewer cases than we currently do.
+ // MathML cells need special treatment. See bug 1002526 comment 56.
+ if (aFrame->IsContainerForFontSizeInflation() &&
+ !aFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ mPresContext = aFrame->PresContext();
+ mOldValue = mPresContext->mInflationDisabledForShrinkWrap;
+ mPresContext->mInflationDisabledForShrinkWrap = true;
+ } else {
+ // indicate we have nothing to restore
+ mPresContext = nullptr;
+ }
+}
+
+AutoMaybeDisableFontInflation::~AutoMaybeDisableFontInflation()
+{
+ if (mPresContext) {
+ mPresContext->mInflationDisabledForShrinkWrap = mOldValue;
+ }
+}
+
+namespace mozilla {
+
+Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel)
+{
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ return Rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+}
+
+Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT)
+{
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ Rect rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+ MaybeSnapToDevicePixels(rect, aSnapDT, true);
+ return rect;
+}
+// Similar to a snapped rect, except an axis is left unsnapped if the snapping
+// process results in a length of 0.
+Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT)
+{
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ Rect rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+ MaybeSnapToDevicePixels(rect, aSnapDT, true, false);
+ return rect;
+}
+
+void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2,
+ int32_t aAppUnitsPerDevPixel,
+ DrawTarget& aDrawTarget,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aDrawOptions)
+{
+ Point p1 = NSPointToPoint(aP1, aAppUnitsPerDevPixel);
+ Point p2 = NSPointToPoint(aP2, aAppUnitsPerDevPixel);
+ SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
+ aStrokeOptions.mLineWidth);
+ aDrawTarget.StrokeLine(p1, p2, aPattern, aStrokeOptions, aDrawOptions);
+}
+
+namespace layout {
+
+
+void
+MaybeSetupTransactionIdAllocator(layers::LayerManager* aManager, nsView* aView)
+{
+ if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_CLIENT) {
+ layers::ClientLayerManager *manager = static_cast<layers::ClientLayerManager*>(aManager);
+ nsRefreshDriver *refresh = aView->GetViewManager()->GetPresShell()->GetPresContext()->RefreshDriver();
+ manager->SetTransactionIdAllocator(refresh);
+ }
+}
+
+} // namespace layout
+} // namespace mozilla
+
+/* static */ bool
+nsLayoutUtils::IsOutlineStyleAutoEnabled()
+{
+ static bool sOutlineStyleAutoEnabled;
+ static bool sOutlineStyleAutoPrefCached = false;
+
+ if (!sOutlineStyleAutoPrefCached) {
+ sOutlineStyleAutoPrefCached = true;
+ Preferences::AddBoolVarCache(&sOutlineStyleAutoEnabled,
+ "layout.css.outline-style-auto.enabled",
+ false);
+ }
+ return sOutlineStyleAutoEnabled;
+}
+
+/* static */ void
+nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame,
+ ReflowOutput& aMetrics,
+ const LogicalMargin& aFramePadding,
+ WritingMode aLineWM,
+ WritingMode aFrameWM)
+{
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+
+ if (fm) {
+ // Compute final height of the frame.
+ //
+ // Do things the standard css2 way -- though it's hard to find it
+ // in the css2 spec! It's actually found in the css1 spec section
+ // 4.4 (you will have to read between the lines to really see
+ // it).
+ //
+ // The height of our box is the sum of our font size plus the top
+ // and bottom border and padding. The height of children do not
+ // affect our height.
+ aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent()
+ : fm->MaxAscent());
+ aMetrics.BSize(aLineWM) = fm->MaxHeight();
+ } else {
+ NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
+ aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
+ }
+ aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
+ aFramePadding.BStart(aFrameWM));
+ aMetrics.BSize(aLineWM) += aFramePadding.BStartEnd(aFrameWM);
+}
+
+/* static */ bool
+nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(nsIPresShell* aShell)
+{
+ if (nsIDocument* doc = aShell->GetDocument()) {
+ WidgetEvent event(true, eVoidEvent);
+ nsTArray<EventTarget*> targets;
+ nsresult rv = EventDispatcher::Dispatch(doc, nullptr, &event, nullptr,
+ nullptr, nullptr, &targets);
+ NS_ENSURE_SUCCESS(rv, false);
+ for (size_t i = 0; i < targets.Length(); i++) {
+ if (targets[i]->IsApzAware()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void
+MaybeReflowForInflationScreenSizeChange(nsPresContext *aPresContext)
+{
+ if (aPresContext) {
+ nsIPresShell* presShell = aPresContext->GetPresShell();
+ bool fontInflationWasEnabled = presShell->FontSizeInflationEnabled();
+ presShell->NotifyFontSizeInflationEnabledIsDirty();
+ bool changed = false;
+ if (presShell && presShell->FontSizeInflationEnabled() &&
+ presShell->FontSizeInflationMinTwips() != 0) {
+ aPresContext->ScreenSizeInchesForFontInflation(&changed);
+ }
+
+ changed = changed ||
+ (fontInflationWasEnabled != presShell->FontSizeInflationEnabled());
+ if (changed) {
+ nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsTArray<nsCOMPtr<nsIContentViewer> > array;
+ cv->AppendSubtree(array);
+ for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
+ nsCOMPtr<nsIPresShell> shell;
+ nsCOMPtr<nsIContentViewer> cv = array[i];
+ cv->GetPresShell(getter_AddRefs(shell));
+ if (shell) {
+ nsIFrame *rootFrame = shell->GetRootFrame();
+ if (rootFrame) {
+ shell->FrameNeedsReflow(rootFrame,
+ nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/* static */ void
+nsLayoutUtils::SetScrollPositionClampingScrollPortSize(nsIPresShell* aPresShell, CSSSize aSize)
+{
+ MOZ_ASSERT(aSize.width >= 0.0 && aSize.height >= 0.0);
+
+ aPresShell->SetScrollPositionClampingScrollPortSize(
+ nsPresContext::CSSPixelsToAppUnits(aSize.width),
+ nsPresContext::CSSPixelsToAppUnits(aSize.height));
+
+ // When the "font.size.inflation.minTwips" preference is set, the
+ // layout depends on the size of the screen. Since when the size
+ // of the screen changes, the scroll position clamping scroll port
+ // size also changes, we hook in the needed updates here rather
+ // than adding a separate notification just for this change.
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ MaybeReflowForInflationScreenSizeChange(presContext);
+}
+
+/* static */ bool
+nsLayoutUtils::CanScrollOriginClobberApz(nsIAtom* aScrollOrigin)
+{
+ return aScrollOrigin != nullptr
+ && aScrollOrigin != nsGkAtoms::apz
+ && aScrollOrigin != nsGkAtoms::restore;
+}
+
+/* static */ ScrollMetadata
+nsLayoutUtils::ComputeScrollMetadata(nsIFrame* aForFrame,
+ nsIFrame* aScrollFrame,
+ nsIContent* aContent,
+ const nsIFrame* aReferenceFrame,
+ Layer* aLayer,
+ ViewID aScrollParentId,
+ const nsRect& aViewport,
+ const Maybe<nsRect>& aClipRect,
+ bool aIsRootContent,
+ const ContainerLayerParameters& aContainerParameters)
+{
+ nsPresContext* presContext = aForFrame->PresContext();
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ nsIPresShell* presShell = presContext->GetPresShell();
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetViewport(CSSRect::FromAppUnits(aViewport));
+
+ ViewID scrollId = FrameMetrics::NULL_SCROLL_ID;
+ if (aContent) {
+ if (void* paintRequestTime = aContent->GetProperty(nsGkAtoms::paintRequestTime)) {
+ metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime));
+ aContent->DeleteProperty(nsGkAtoms::paintRequestTime);
+ }
+ scrollId = nsLayoutUtils::FindOrCreateIDFor(aContent);
+ nsRect dp;
+ if (nsLayoutUtils::GetDisplayPort(aContent, &dp)) {
+ metrics.SetDisplayPort(CSSRect::FromAppUnits(dp));
+ nsLayoutUtils::LogTestDataForPaint(aLayer->Manager(), scrollId, "displayport",
+ metrics.GetDisplayPort());
+ }
+ if (nsLayoutUtils::GetCriticalDisplayPort(aContent, &dp)) {
+ metrics.SetCriticalDisplayPort(CSSRect::FromAppUnits(dp));
+ nsLayoutUtils::LogTestDataForPaint(aLayer->Manager(), scrollId,
+ "criticalDisplayport", metrics.GetCriticalDisplayPort());
+ }
+ DisplayPortMarginsPropertyData* marginsData =
+ static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+ if (marginsData) {
+ metrics.SetDisplayPortMargins(marginsData->mMargins);
+ }
+ }
+
+ nsIScrollableFrame* scrollableFrame = nullptr;
+ if (aScrollFrame)
+ scrollableFrame = aScrollFrame->GetScrollTargetFrame();
+
+ metrics.SetScrollableRect(CSSRect::FromAppUnits(
+ nsLayoutUtils::CalculateScrollableRectForFrame(scrollableFrame, aForFrame)));
+
+ if (scrollableFrame) {
+ nsPoint scrollPosition = scrollableFrame->GetScrollPosition();
+ metrics.SetScrollOffset(CSSPoint::FromAppUnits(scrollPosition));
+
+ nsPoint smoothScrollPosition = scrollableFrame->LastScrollDestination();
+ metrics.SetSmoothScrollOffset(CSSPoint::FromAppUnits(smoothScrollPosition));
+
+ // If the frame was scrolled since the last layers update, and by something
+ // that is higher priority than APZ, we want to tell the APZ to update
+ // its scroll offset. We want to distinguish the case where the scroll offset
+ // was "restored" because in that case the restored scroll position should
+ // not overwrite a user-driven scroll.
+ if (scrollableFrame->LastScrollOrigin() == nsGkAtoms::restore) {
+ metrics.SetScrollOffsetRestored(scrollableFrame->CurrentScrollGeneration());
+ } else if (CanScrollOriginClobberApz(scrollableFrame->LastScrollOrigin())) {
+ metrics.SetScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration());
+ }
+ scrollableFrame->AllowScrollOriginDowngrade();
+
+ nsIAtom* lastSmoothScrollOrigin = scrollableFrame->LastSmoothScrollOrigin();
+ if (lastSmoothScrollOrigin) {
+ metrics.SetSmoothScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration());
+ }
+
+ nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount();
+ LayoutDeviceIntSize lineScrollAmountInDevPixels =
+ LayoutDeviceIntSize::FromAppUnitsRounded(lineScrollAmount, presContext->AppUnitsPerDevPixel());
+ metadata.SetLineScrollAmount(lineScrollAmountInDevPixels);
+
+ nsSize pageScrollAmount = scrollableFrame->GetPageScrollAmount();
+ LayoutDeviceIntSize pageScrollAmountInDevPixels =
+ LayoutDeviceIntSize::FromAppUnitsRounded(pageScrollAmount, presContext->AppUnitsPerDevPixel());
+ metadata.SetPageScrollAmount(pageScrollAmountInDevPixels);
+
+ if (!aScrollFrame->GetParent() ||
+ EventStateManager::CanVerticallyScrollFrameWithWheel(aScrollFrame->GetParent()))
+ {
+ metadata.SetAllowVerticalScrollWithWheel(true);
+ }
+
+ metadata.SetUsesContainerScrolling(scrollableFrame->UsesContainerScrolling());
+
+ metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo());
+ }
+
+ // If we have the scrollparent being the same as the scroll id, the
+ // compositor-side code could get into an infinite loop while building the
+ // overscroll handoff chain.
+ MOZ_ASSERT(aScrollParentId == FrameMetrics::NULL_SCROLL_ID || scrollId != aScrollParentId);
+ metrics.SetScrollId(scrollId);
+ metrics.SetIsRootContent(aIsRootContent);
+ metadata.SetScrollParentId(aScrollParentId);
+
+ if (scrollId != FrameMetrics::NULL_SCROLL_ID && !presContext->GetParentPresContext()) {
+ if ((aScrollFrame && (aScrollFrame == presShell->GetRootScrollFrame())) ||
+ aContent == presShell->GetDocument()->GetDocumentElement()) {
+ metadata.SetIsLayersIdRoot(true);
+ }
+ }
+
+ // Only the root scrollable frame for a given presShell should pick up
+ // the presShell's resolution. All the other frames are 1.0.
+ if (aScrollFrame == presShell->GetRootScrollFrame()) {
+ metrics.SetPresShellResolution(presShell->GetResolution());
+ } else {
+ metrics.SetPresShellResolution(1.0f);
+ }
+ // The cumulative resolution is the resolution at which the scroll frame's
+ // content is actually rendered. It includes the pres shell resolutions of
+ // all the pres shells from here up to the root, as well as any css-driven
+ // resolution. We don't need to compute it as it's already stored in the
+ // container parameters.
+ metrics.SetCumulativeResolution(aContainerParameters.Scale());
+
+ LayoutDeviceToScreenScale2D resolutionToScreen(
+ presShell->GetCumulativeResolution()
+ * nsLayoutUtils::GetTransformToAncestorScale(aScrollFrame ? aScrollFrame : aForFrame));
+ metrics.SetExtraResolution(metrics.GetCumulativeResolution() / resolutionToScreen);
+
+ metrics.SetDevPixelsPerCSSPixel(presContext->CSSToDevPixelScale());
+
+ // Initially, AsyncPanZoomController should render the content to the screen
+ // at the painted resolution.
+ const LayerToParentLayerScale layerToParentLayerScale(1.0f);
+ metrics.SetZoom(metrics.GetCumulativeResolution() * metrics.GetDevPixelsPerCSSPixel()
+ * layerToParentLayerScale);
+
+ // Calculate the composition bounds as the size of the scroll frame and
+ // its origin relative to the reference frame.
+ // If aScrollFrame is null, we are in a document without a root scroll frame,
+ // so it's a xul document. In this case, use the size of the viewport frame.
+ nsIFrame* frameForCompositionBoundsCalculation = aScrollFrame ? aScrollFrame : aForFrame;
+ nsRect compositionBounds(frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aReferenceFrame),
+ frameForCompositionBoundsCalculation->GetSize());
+ if (scrollableFrame) {
+ // If we have a scrollable frame, restrict the composition bounds to its
+ // scroll port. The scroll port excludes the frame borders and the scroll
+ // bars, which we don't want to be part of the composition bounds.
+ nsRect scrollPort = scrollableFrame->GetScrollPortRect();
+ compositionBounds = nsRect(compositionBounds.TopLeft() + scrollPort.TopLeft(),
+ scrollPort.Size());
+ }
+ ParentLayerRect frameBounds = LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel)
+ * metrics.GetCumulativeResolution()
+ * layerToParentLayerScale;
+
+ if (aClipRect) {
+ ParentLayerRect rect = LayoutDeviceRect::FromAppUnits(*aClipRect, auPerDevPixel)
+ * metrics.GetCumulativeResolution()
+ * layerToParentLayerScale;
+ metadata.SetScrollClip(Some(LayerClip(RoundedToInt(rect))));
+ }
+
+ // For the root scroll frame of the root content document (RCD-RSF), the above calculation
+ // will yield the size of the viewport frame as the composition bounds, which
+ // doesn't actually correspond to what is visible when
+ // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible area of
+ // the prescontext that the viewport frame is reflowed into. In that case if our
+ // document has a widget then the widget's bounds will correspond to what is
+ // visible. If we don't have a widget the root view's bounds correspond to what
+ // would be visible because they don't get modified by setCSSViewport.
+ bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
+ bool isRootContentDocRootScrollFrame = isRootScrollFrame
+ && presContext->IsRootContentDocument();
+ if (isRootContentDocRootScrollFrame) {
+ UpdateCompositionBoundsForRCDRSF(frameBounds, presContext, true);
+ }
+
+ nsMargin sizes = ScrollbarAreaToExcludeFromCompositionBoundsFor(aScrollFrame);
+ // Scrollbars are not subject to resolution scaling, so LD pixels = layer pixels for them.
+ ParentLayerMargin boundMargins = LayoutDeviceMargin::FromAppUnits(sizes, auPerDevPixel)
+ * LayoutDeviceToParentLayerScale(1.0f);
+ frameBounds.Deflate(boundMargins);
+
+ metrics.SetCompositionBounds(frameBounds);
+
+ metrics.SetRootCompositionSize(
+ nsLayoutUtils::CalculateRootCompositionSize(aScrollFrame ? aScrollFrame : aForFrame,
+ isRootContentDocRootScrollFrame, metrics));
+
+ if (gfxPrefs::APZPrintTree() || gfxPrefs::APZTestLoggingEnabled()) {
+ if (nsIContent* content = frameForCompositionBoundsCalculation->GetContent()) {
+ nsAutoString contentDescription;
+ content->Describe(contentDescription);
+ metadata.SetContentDescription(NS_LossyConvertUTF16toASCII(contentDescription));
+ nsLayoutUtils::LogTestDataForPaint(aLayer->Manager(), scrollId, "contentDescription",
+ metadata.GetContentDescription().get());
+ }
+ }
+
+ metrics.SetPresShellId(presShell->GetPresShellId());
+
+ // If the scroll frame's content is marked 'scrollgrab', record this
+ // in the FrameMetrics so APZ knows to provide the scroll grabbing
+ // behaviour.
+ if (aScrollFrame && nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
+ metadata.SetHasScrollgrab(true);
+ }
+
+ // Also compute and set the background color.
+ // This is needed for APZ overscrolling support.
+ if (aScrollFrame) {
+ if (isRootScrollFrame) {
+ metadata.SetBackgroundColor(Color::FromABGR(
+ presShell->GetCanvasBackground()));
+ } else {
+ nsStyleContext* backgroundStyle;
+ if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) {
+ metadata.SetBackgroundColor(Color::FromABGR(
+ backgroundStyle->StyleBackground()->mBackgroundColor));
+ }
+ }
+ }
+
+ if (ShouldDisableApzForElement(aContent)) {
+ metadata.SetForceDisableApz(true);
+ }
+
+ return metadata;
+}
+
+/* static */ bool
+nsLayoutUtils::ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId)
+{
+ for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) {
+ if (aLayer->GetFrameMetrics(i-1).GetScrollId() == aScrollId) {
+ return true;
+ }
+ }
+ for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) {
+ if (ContainsMetricsWithId(child, aScrollId)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ uint32_t
+nsLayoutUtils::GetTouchActionFromFrame(nsIFrame* aFrame)
+{
+ // If aFrame is null then return default value
+ if (!aFrame) {
+ return NS_STYLE_TOUCH_ACTION_AUTO;
+ }
+
+ // The touch-action CSS property applies to: all elements except:
+ // non-replaced inline elements, table rows, row groups, table columns, and column groups
+ bool isNonReplacedInlineElement = aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
+ if (isNonReplacedInlineElement) {
+ return NS_STYLE_TOUCH_ACTION_AUTO;
+ }
+
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ bool isTableElement = disp->IsInnerTableStyle() &&
+ disp->mDisplay != StyleDisplay::TableCell &&
+ disp->mDisplay != StyleDisplay::TableCaption;
+ if (isTableElement) {
+ return NS_STYLE_TOUCH_ACTION_AUTO;
+ }
+
+ return disp->mTouchAction;
+}
+
+/* static */ void
+nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ const nsRegion& aRegion,
+ nsIFrame* aFrame,
+ const nsIFrame* aAncestorFrame,
+ nsRegion* aPreciseTargetDest,
+ nsRegion* aImpreciseTargetDest,
+ Maybe<Matrix4x4>* aMatrixCache)
+{
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+ bool isPrecise;
+ RegionBuilder<nsRegion> transformedRegion;
+ for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) {
+ nsRect transformed = TransformFrameRectToAncestor(
+ aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache);
+ transformedRegion.OrWith(transformed);
+ }
+ nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest;
+ dest->OrWith(transformedRegion.ToRegion());
+}
+
+/* static */ bool
+nsLayoutUtils::ShouldUseNoScriptSheet(nsIDocument* aDocument)
+{
+ // also handle the case where print is done from print preview
+ // see bug #342439 for more details
+ if (aDocument->IsStaticDocument()) {
+ aDocument = aDocument->GetOriginalDocument();
+ }
+ return aDocument->IsScriptEnabled();
+}
+
+/* static */ bool
+nsLayoutUtils::ShouldUseNoFramesSheet(nsIDocument* aDocument)
+{
+ bool allowSubframes = true;
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (docShell) {
+ docShell->GetAllowSubframes(&allowSubframes);
+ }
+ return !allowSubframes;
+}
+
+/* static */ void
+nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult)
+{
+ aResult.Truncate();
+ AppendFrameTextContent(aFrame, aResult);
+}
+
+/* static */ void
+nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult)
+{
+ if (aFrame->GetType() == nsGkAtoms::textFrame) {
+ auto textFrame = static_cast<nsTextFrame*>(aFrame);
+ auto offset = textFrame->GetContentOffset();
+ auto length = textFrame->GetContentLength();
+ textFrame->GetContent()->
+ GetText()->AppendTo(aResult, offset, length);
+ } else {
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ AppendFrameTextContent(child, aResult);
+ }
+ }
+}
+
+/* static */
+nsRect
+nsLayoutUtils::GetSelectionBoundingRect(Selection* aSel)
+{
+ nsRect res;
+ // Bounding client rect may be empty after calling GetBoundingClientRect
+ // when range is collapsed. So we get caret's rect when range is
+ // collapsed.
+ if (aSel->IsCollapsed()) {
+ nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
+ if (frame) {
+ nsIFrame* relativeTo = GetContainingBlockForClientRect(frame);
+ res = TransformFrameRectToAncestor(frame, res, relativeTo);
+ }
+ } else {
+ int32_t rangeCount = aSel->RangeCount();
+ RectAccumulator accumulator;
+ for (int32_t idx = 0; idx < rangeCount; ++idx) {
+ nsRange* range = aSel->GetRangeAt(idx);
+ nsRange::CollectClientRectsAndText(&accumulator, nullptr, range,
+ range->GetStartParent(), range->StartOffset(),
+ range->GetEndParent(), range->EndOffset(),
+ true, false);
+ }
+ res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
+ accumulator.mResultRect;
+ }
+
+ return res;
+}
+
+/* static */ nsBlockFrame*
+nsLayoutUtils::GetFloatContainingBlock(nsIFrame* aFrame)
+{
+ nsIFrame* ancestor = aFrame->GetParent();
+ while (ancestor && !ancestor->IsFloatContainingBlock()) {
+ ancestor = ancestor->GetParent();
+ }
+ MOZ_ASSERT(!ancestor || GetAsBlock(ancestor),
+ "Float containing block can only be block frame");
+ return static_cast<nsBlockFrame*>(ancestor);
+}
+
+// The implementation of this calculation is adapted from
+// Element::GetBoundingClientRect().
+/* static */ CSSRect
+nsLayoutUtils::GetBoundingContentRect(const nsIContent* aContent,
+ const nsIScrollableFrame* aRootScrollFrame) {
+ CSSRect result;
+ if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
+ nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
+ result = CSSRect::FromAppUnits(
+ nsLayoutUtils::GetAllInFlowRectsUnion(
+ frame,
+ relativeTo,
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+
+ // If the element is contained in a scrollable frame that is not
+ // the root scroll frame, make sure to clip the result so that it is
+ // not larger than the containing scrollable frame's bounds.
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(frame);
+ if (scrollFrame && scrollFrame != aRootScrollFrame) {
+ nsIFrame* subFrame = do_QueryFrame(scrollFrame);
+ MOZ_ASSERT(subFrame);
+ // Get the bounds of the scroll frame in the same coordinate space
+ // as |result|.
+ CSSRect subFrameRect = CSSRect::FromAppUnits(
+ nsLayoutUtils::TransformFrameRectToAncestor(
+ subFrame,
+ subFrame->GetRectRelativeToSelf(),
+ relativeTo));
+
+ result = subFrameRect.Intersect(result);
+ }
+ }
+ return result;
+}
+
+static already_AddRefed<nsIPresShell>
+GetPresShell(const nsIContent* aContent)
+{
+ nsCOMPtr<nsIPresShell> result;
+ if (nsIDocument* doc = aContent->GetComposedDoc()) {
+ result = doc->GetShell();
+ }
+ return result.forget();
+}
+
+static void UpdateDisplayPortMarginsForPendingMetrics(FrameMetrics& aMetrics) {
+ nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+ if (!content) {
+ return;
+ }
+
+ nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
+ if (!shell) {
+ return;
+ }
+
+ MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+
+ if (gfxPrefs::APZAllowZooming() && aMetrics.IsRootContent()) {
+ // See APZCCallbackHelper::UpdateRootFrame for details.
+ float presShellResolution = shell->GetResolution();
+ if (presShellResolution != aMetrics.GetPresShellResolution()) {
+ return;
+ }
+ }
+
+ nsIScrollableFrame* frame = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+
+ if (!frame) {
+ return;
+ }
+
+ if (APZCCallbackHelper::IsScrollInProgress(frame)) {
+ // If these conditions are true, then the UpdateFrame
+ // message may be ignored by the main-thread, so we
+ // shouldn't update the displayport based on it.
+ return;
+ }
+
+ DisplayPortMarginsPropertyData* currentData =
+ static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
+ if (!currentData) {
+ return;
+ }
+
+ CSSPoint frameScrollOffset = CSSPoint::FromAppUnits(frame->GetScrollPosition());
+ APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset);
+
+ nsLayoutUtils::SetDisplayPortMargins(content, shell,
+ aMetrics.GetDisplayPortMargins(), 0);
+}
+
+/* static */ void
+nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages()
+{
+ if (mozilla::dom::ContentChild::GetSingleton() &&
+ mozilla::dom::ContentChild::GetSingleton()->GetIPCChannel()) {
+ CompositorBridgeChild::Get()->GetIPCChannel()->PeekMessages(
+ [](const IPC::Message& aMsg) -> bool {
+ if (aMsg.type() == mozilla::layers::PAPZ::Msg_RequestContentRepaint__ID) {
+ PickleIterator iter(aMsg);
+ FrameMetrics frame;
+ if (!IPC::ReadParam(&aMsg, &iter, &frame)) {
+ MOZ_ASSERT(false);
+ return true;
+ }
+
+ UpdateDisplayPortMarginsForPendingMetrics(frame);
+ }
+ return true;
+ });
+ }
+}
+
+/* static */ bool
+nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame)
+{
+ for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
+ if (f->IsTransformed()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*static*/ CSSPoint
+nsLayoutUtils::GetCumulativeApzCallbackTransform(nsIFrame* aFrame)
+{
+ CSSPoint delta;
+ if (!aFrame) {
+ return delta;
+ }
+ nsIFrame* frame = aFrame;
+ nsCOMPtr<nsIContent> content = frame->GetContent();
+ nsCOMPtr<nsIContent> lastContent;
+ while (frame) {
+ if (content && (content != lastContent)) {
+ void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
+ if (property) {
+ delta += *static_cast<CSSPoint*>(property);
+ }
+ }
+ frame = GetCrossDocParentFrame(frame);
+ lastContent = content;
+ content = frame ? frame->GetContent() : nullptr;
+ }
+ return delta;
+}
+
+/* static */ bool
+nsLayoutUtils::SupportsServoStyleBackend(nsIDocument* aDocument)
+{
+ return StyloEnabled() &&
+ aDocument->IsHTMLOrXHTML() &&
+ static_cast<nsDocument*>(aDocument)->IsContentDocument();
+}
+
+static
+bool
+LineHasNonEmptyContentWorker(nsIFrame* aFrame)
+{
+ // Look for non-empty frames, but ignore inline and br frames.
+ // For inline frames, descend into the children, if any.
+ if (aFrame->GetType() == nsGkAtoms::inlineFrame) {
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ if (LineHasNonEmptyContentWorker(child)) {
+ return true;
+ }
+ }
+ } else {
+ if (aFrame->GetType() != nsGkAtoms::brFrame &&
+ !aFrame->IsEmpty()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static
+bool
+LineHasNonEmptyContent(nsLineBox* aLine)
+{
+ int32_t count = aLine->GetChildCount();
+ for (nsIFrame* frame = aLine->mFirstChild; count > 0;
+ --count, frame = frame->GetNextSibling()) {
+ if (LineHasNonEmptyContentWorker(frame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ bool
+nsLayoutUtils::IsInvisibleBreak(nsINode* aNode, nsIFrame** aNextLineFrame)
+{
+ if (aNextLineFrame) {
+ *aNextLineFrame = nullptr;
+ }
+
+ if (!aNode->IsElement() || !aNode->IsEditable()) {
+ return false;
+ }
+ nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
+ if (!frame || frame->GetType() != nsGkAtoms::brFrame) {
+ return false;
+ }
+
+ nsContainerFrame* f = frame->GetParent();
+ while (f && f->IsFrameOfType(nsBox::eLineParticipant)) {
+ f = f->GetParent();
+ }
+ nsBlockFrame* blockAncestor = do_QueryFrame(f);
+ if (!blockAncestor) {
+ // The container frame doesn't support line breaking.
+ return false;
+ }
+
+ bool valid = false;
+ nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
+ if (!valid) {
+ return false;
+ }
+
+ bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
+ if (!lineNonEmpty) {
+ return false;
+ }
+
+ while (iter.Next()) {
+ auto currentLine = iter.GetLine();
+ // Completely skip empty lines.
+ if (!currentLine->IsEmpty()) {
+ // If we come across an inline line, the BR has caused a visible line break.
+ if (currentLine->IsInline()) {
+ if (aNextLineFrame) {
+ *aNextLineFrame = currentLine->mFirstChild;
+ }
+ return false;
+ }
+ break;
+ }
+ }
+
+ return lineNonEmpty;
+}
diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h
new file mode 100644
index 000000000..d9580a3df
--- /dev/null
+++ b/layout/base/nsLayoutUtils.h
@@ -0,0 +1,3041 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLayoutUtils_h__
+#define nsLayoutUtils_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsBoundingMetrics.h"
+#include "nsChangeHint.h"
+#include "nsFrameList.h"
+#include "mozilla/layout/FrameChildList.h"
+#include "nsThreadUtils.h"
+#include "nsIPrincipal.h"
+#include "FrameMetrics.h"
+#include "nsIWidget.h"
+#include "nsCSSPropertyID.h"
+#include "nsStyleCoord.h"
+#include "nsStyleConsts.h"
+#include "nsGkAtoms.h"
+#include "nsRuleNode.h"
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+#include "mozilla/ToString.h"
+#include "mozilla/ReflowOutput.h"
+#include "ImageContainer.h"
+#include "gfx2DGlue.h"
+
+#include <limits>
+#include <algorithm>
+
+class nsPresContext;
+class nsIContent;
+class nsIAtom;
+class nsIScrollableFrame;
+class nsIDOMEvent;
+class nsRegion;
+class nsDisplayListBuilder;
+enum class nsDisplayListBuilderMode : uint8_t;
+class nsDisplayItem;
+class nsFontMetrics;
+class nsFontFaceList;
+class nsIImageLoadingContent;
+class nsStyleContext;
+class nsBlockFrame;
+class nsContainerFrame;
+class nsView;
+class nsIFrame;
+class nsStyleCoord;
+class nsStyleCorners;
+class gfxContext;
+class nsPIDOMWindowOuter;
+class imgIRequest;
+class nsIDocument;
+struct gfxPoint;
+struct nsStyleFont;
+struct nsStyleImageOrientation;
+struct nsOverflowAreas;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+class EventListenerManager;
+class SVGImageContext;
+struct IntrinsicSize;
+struct ContainerLayerParameters;
+class WritingMode;
+namespace dom {
+class CanvasRenderingContext2D;
+class DOMRectList;
+class Element;
+class HTMLImageElement;
+class HTMLCanvasElement;
+class HTMLVideoElement;
+class OffscreenCanvas;
+class Selection;
+} // namespace dom
+namespace gfx {
+struct RectCornerRadii;
+} // namespace gfx
+namespace layers {
+class Image;
+class Layer;
+} // namespace layers
+} // namespace mozilla
+
+namespace mozilla {
+
+struct DisplayPortPropertyData {
+ DisplayPortPropertyData(const nsRect& aRect, uint32_t aPriority)
+ : mRect(aRect)
+ , mPriority(aPriority)
+ {}
+ nsRect mRect;
+ uint32_t mPriority;
+};
+
+struct DisplayPortMarginsPropertyData {
+ DisplayPortMarginsPropertyData(const ScreenMargin& aMargins,
+ uint32_t aPriority)
+ : mMargins(aMargins)
+ , mPriority(aPriority)
+ {}
+ ScreenMargin mMargins;
+ uint32_t mPriority;
+};
+
+} // namespace mozilla
+
+// For GetDisplayPort
+enum class RelativeTo {
+ ScrollPort,
+ ScrollFrame
+};
+
+/**
+ * nsLayoutUtils is a namespace class used for various helper
+ * functions that are useful in multiple places in layout. The goal
+ * is not to define multiple copies of the same static helper.
+ */
+class nsLayoutUtils
+{
+ typedef mozilla::dom::DOMRectList DOMRectList;
+ typedef mozilla::layers::Layer Layer;
+ typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
+ typedef mozilla::IntrinsicSize IntrinsicSize;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::ExtendMode ExtendMode;
+ typedef mozilla::gfx::SamplingFilter SamplingFilter;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectDouble RectDouble;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollMetadata ScrollMetadata;
+ typedef FrameMetrics::ViewID ViewID;
+ typedef mozilla::CSSPoint CSSPoint;
+ typedef mozilla::CSSSize CSSSize;
+ typedef mozilla::CSSIntSize CSSIntSize;
+ typedef mozilla::CSSRect CSSRect;
+ typedef mozilla::ScreenMargin ScreenMargin;
+ typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
+
+ /**
+ * Finds previously assigned ViewID for the given content element, if any.
+ * Returns whether a ViewID was previously assigned.
+ */
+ static bool FindIDFor(const nsIContent* aContent, ViewID* aOutViewId);
+
+ /**
+ * Finds previously assigned or generates a unique ViewID for the given
+ * content element.
+ */
+ static ViewID FindOrCreateIDFor(nsIContent* aContent);
+
+ /**
+ * Find content for given ID.
+ */
+ static nsIContent* FindContentFor(ViewID aId);
+
+ /**
+ * Find the scrollable frame for a given ID.
+ */
+ static nsIScrollableFrame* FindScrollableFrameFor(ViewID aId);
+
+ /**
+ * Get display port for the given element, relative to the specified entity,
+ * defaulting to the scrollport.
+ */
+ static bool GetDisplayPort(nsIContent* aContent, nsRect *aResult,
+ RelativeTo aRelativeTo = RelativeTo::ScrollPort);
+
+ /**
+ * Check whether the given element has a displayport.
+ */
+ static bool HasDisplayPort(nsIContent* aContent);
+
+
+ /**
+ * Go through the IPC Channel and update displayport margins for content
+ * elements based on UpdateFrame messages. The messages are left in the
+ * queue and will be fully processed when dequeued. The aim is to paint
+ * the most up-to-date displayport without waiting for these message to
+ * go through the message queue.
+ */
+ static void UpdateDisplayPortMarginsFromPendingMessages();
+
+ /**
+ * @return the display port for the given element which should be used for
+ * visibility testing purposes.
+ *
+ * If low-precision buffers are enabled, this is the critical display port;
+ * otherwise, it's the same display port returned by GetDisplayPort().
+ */
+ static bool GetDisplayPortForVisibilityTesting(
+ nsIContent* aContent,
+ nsRect* aResult,
+ RelativeTo aRelativeTo = RelativeTo::ScrollPort);
+
+ enum class RepaintMode : uint8_t {
+ Repaint,
+ DoNotRepaint
+ };
+
+ /**
+ * 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.
+ */
+ static bool SetDisplayPortMargins(nsIContent* aContent,
+ nsIPresShell* aPresShell,
+ const ScreenMargin& aMargins,
+ 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);
+
+ /**
+ * Get the critical display port for the given element.
+ */
+ static bool GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult);
+
+ /**
+ * Check whether the given element has a critical display port.
+ */
+ static bool HasCriticalDisplayPort(nsIContent* aContent);
+
+ /**
+ * If low-precision painting is turned on, delegates to GetCriticalDisplayPort.
+ * Otherwise, delegates to GetDisplayPort.
+ */
+ static bool GetHighResolutionDisplayPort(nsIContent* aContent, nsRect* aResult);
+
+ /**
+ * Remove the displayport for the given element.
+ */
+ static void RemoveDisplayPort(nsIContent* aContent);
+
+ /**
+ * Use heuristics to figure out the child list that
+ * aChildFrame is currently in.
+ */
+ static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
+
+ /**
+ * GetBeforeFrameForContent returns the ::before frame for aContent, if
+ * one exists. This is typically O(1). The frame passed in must be
+ * the first-in-flow.
+ *
+ * @param aGenConParentFrame an ancestor of the ::before frame
+ * @param aContent the content whose ::before is wanted
+ * @return the ::before frame or nullptr if there isn't one
+ */
+ static nsIFrame* GetBeforeFrameForContent(nsIFrame* aGenConParentFrame,
+ const nsIContent* aContent);
+
+ /**
+ * GetBeforeFrame returns the outermost ::before frame of the given frame, if
+ * one exists. This is typically O(1). The frame passed in must be
+ * the first-in-flow.
+ *
+ * @param aFrame the frame whose ::before is wanted
+ * @return the :before frame or nullptr if there isn't one
+ */
+ static nsIFrame* GetBeforeFrame(nsIFrame* aFrame);
+
+ /**
+ * GetAfterFrameForContent returns the ::after frame for aContent, if one
+ * exists. This will walk the in-flow chain of aGenConParentFrame to the
+ * last-in-flow if needed. This function is typically O(N) in the number
+ * of child frames, following in-flows, etc.
+ *
+ * @param aGenConParentFrame an ancestor of the ::after frame
+ * @param aContent the content whose ::after is wanted
+ * @return the ::after frame or nullptr if there isn't one
+ */
+ static nsIFrame* GetAfterFrameForContent(nsIFrame* aGenConParentFrame,
+ const nsIContent* aContent);
+
+ /**
+ * GetAfterFrame returns the outermost ::after frame of the given frame, if one
+ * exists. This will walk the in-flow chain to the last-in-flow if
+ * needed. This function is typically O(N) in the number of child
+ * frames, following in-flows, etc.
+ *
+ * @param aFrame the frame whose ::after is wanted
+ * @return the :after frame or nullptr if there isn't one
+ */
+ static nsIFrame* GetAfterFrame(nsIFrame* aFrame);
+
+ /**
+ * Given a frame, search up the frame tree until we find an
+ * ancestor that (or the frame itself) is of type aFrameType, if any.
+ *
+ * @param aFrame the frame to start at
+ * @param aFrameType the frame type to look for
+ * @param aStopAt a frame to stop at after we checked it
+ * @return a frame of the given type or nullptr if no
+ * such ancestor exists
+ */
+ static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame,
+ nsIAtom* aFrameType,
+ nsIFrame* aStopAt = nullptr);
+
+ /**
+ * Given a frame, search up the frame tree until we find an
+ * ancestor that (or the frame itself) is a "Page" frame, if any.
+ *
+ * @param aFrame the frame to start at
+ * @return a frame of type nsGkAtoms::pageFrame or nullptr if no
+ * such ancestor exists
+ */
+ static nsIFrame* GetPageFrame(nsIFrame* aFrame)
+ {
+ return GetClosestFrameOfType(aFrame, nsGkAtoms::pageFrame);
+ }
+
+ /**
+ * Given a frame which is the primary frame for an element,
+ * return the frame that has the non-pseudoelement style context for
+ * the content.
+ * This is aPrimaryFrame itself except for tableWrapper frames.
+ *
+ * Given a non-null input, this will return null if and only if its
+ * argument is a table wrapper frame that is mid-destruction (and its
+ * table frame has been destroyed).
+ */
+ static nsIFrame* GetStyleFrame(nsIFrame* aPrimaryFrame);
+
+ /**
+ * Given a content node,
+ * return the frame that has the non-pseudoelement style context for
+ * the content. May return null.
+ * This is aContent->GetPrimaryFrame() except for tableWrapper frames.
+ */
+ static nsIFrame* GetStyleFrame(const nsIContent* aContent);
+
+ /**
+ * Gets the real primary frame associated with the content object.
+ *
+ * In the case of absolutely positioned elements and floated elements,
+ * the real primary frame is the frame that is out of the flow and not the
+ * placeholder frame.
+ */
+ static nsIFrame* GetRealPrimaryFrameFor(const nsIContent* aContent);
+
+ /**
+ * IsGeneratedContentFor returns true if aFrame is the outermost
+ * frame for generated content of type aPseudoElement for aContent.
+ * aFrame *might not* have the aPseudoElement pseudo-style! For example
+ * it might be a table wrapper frame and the inner table frame might
+ * have the pseudo-style.
+ *
+ * @param aContent the content node we're looking at. If this is
+ * null, then we just assume that aFrame has the right content
+ * pointer.
+ * @param aFrame the frame we're looking at
+ * @param aPseudoElement the pseudo type we're interested in
+ * @return whether aFrame is the generated aPseudoElement frame for aContent
+ */
+ static bool IsGeneratedContentFor(nsIContent* aContent, nsIFrame* aFrame,
+ nsIAtom* aPseudoElement);
+
+#ifdef DEBUG
+ // TODO: remove, see bug 598468.
+ static bool gPreventAssertInCompareTreePosition;
+#endif // DEBUG
+
+ /**
+ * CompareTreePosition determines whether aContent1 comes before or
+ * after aContent2 in a preorder traversal of the content tree.
+ *
+ * @param aCommonAncestor either null, or a common ancestor of
+ * aContent1 and aContent2. Actually this is
+ * only a hint; if it's not an ancestor of
+ * aContent1 or aContent2, this function will
+ * still work, but it will be slower than
+ * normal.
+ * @return < 0 if aContent1 is before aContent2
+ * > 0 if aContent1 is after aContent2,
+ * 0 otherwise (meaning they're the same, or they're in
+ * different documents)
+ */
+ static int32_t CompareTreePosition(nsIContent* aContent1,
+ nsIContent* aContent2,
+ const nsIContent* aCommonAncestor = nullptr)
+ {
+ return DoCompareTreePosition(aContent1, aContent2, -1, 1, aCommonAncestor);
+ }
+
+ /*
+ * More generic version of |CompareTreePosition|. |aIf1Ancestor|
+ * gives the value to return when 1 is an ancestor of 2, and likewise
+ * for |aIf2Ancestor|. Passing (-1, 1) gives preorder traversal
+ * order, and (1, -1) gives postorder traversal order.
+ */
+ static int32_t DoCompareTreePosition(nsIContent* aContent1,
+ nsIContent* aContent2,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ const nsIContent* aCommonAncestor = nullptr);
+
+ /**
+ * CompareTreePosition determines whether aFrame1 comes before or
+ * after aFrame2 in a preorder traversal of the frame tree, where out
+ * of flow frames are treated as children of their placeholders. This is
+ * basically the same ordering as DoCompareTreePosition(nsIContent*) except
+ * that it handles anonymous content properly and there are subtleties with
+ * continuations.
+ *
+ * @param aCommonAncestor either null, or a common ancestor of
+ * aContent1 and aContent2. Actually this is
+ * only a hint; if it's not an ancestor of
+ * aContent1 or aContent2, this function will
+ * still work, but it will be slower than
+ * normal.
+ * @return < 0 if aContent1 is before aContent2
+ * > 0 if aContent1 is after aContent2,
+ * 0 otherwise (meaning they're the same, or they're in
+ * different frame trees)
+ */
+ static int32_t CompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ nsIFrame* aCommonAncestor = nullptr)
+ {
+ return DoCompareTreePosition(aFrame1, aFrame2, -1, 1, aCommonAncestor);
+ }
+
+ static int32_t CompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ nsTArray<nsIFrame*>& aFrame2Ancestors,
+ nsIFrame* aCommonAncestor = nullptr)
+ {
+ return DoCompareTreePosition(aFrame1, aFrame2, aFrame2Ancestors,
+ -1, 1, aCommonAncestor);
+ }
+
+ /*
+ * More generic version of |CompareTreePosition|. |aIf1Ancestor|
+ * gives the value to return when 1 is an ancestor of 2, and likewise
+ * for |aIf2Ancestor|. Passing (-1, 1) gives preorder traversal
+ * order, and (1, -1) gives postorder traversal order.
+ */
+ static int32_t DoCompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ nsIFrame* aCommonAncestor = nullptr);
+
+ static nsIFrame* FillAncestors(nsIFrame* aFrame,
+ nsIFrame* aStopAtAncestor,
+ nsTArray<nsIFrame*>* aAncestors);
+
+ static int32_t DoCompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ nsTArray<nsIFrame*>& aFrame2Ancestors,
+ int32_t aIf1Ancestor,
+ int32_t aIf2Ancestor,
+ nsIFrame* aCommonAncestor);
+
+ /**
+ * LastContinuationWithChild gets the last continuation in aFrame's chain
+ * that has a child, or the first continuation if the frame has no children.
+ */
+ static nsContainerFrame* LastContinuationWithChild(nsContainerFrame* aFrame);
+
+ /**
+ * GetLastSibling simply finds the last sibling of aFrame, or returns nullptr if
+ * aFrame is null.
+ */
+ static nsIFrame* GetLastSibling(nsIFrame* aFrame);
+
+ /**
+ * FindSiblingViewFor locates the child of aParentView that aFrame's
+ * view should be inserted 'above' (i.e., before in sibling view
+ * order). This is the first child view of aParentView whose
+ * corresponding content is before aFrame's content (view siblings
+ * are in reverse content order).
+ */
+ static nsView* FindSiblingViewFor(nsView* aParentView, nsIFrame* aFrame);
+
+ /**
+ * Get the parent of aFrame. If aFrame is the root frame for a document,
+ * and the document has a parent document in the same view hierarchy, then
+ * we try to return the subdocumentframe in the parent document.
+ * @param aExtraOffset [in/out] if non-null, then as we cross documents
+ * an extra offset may be required and it will be added to aCrossDocOffset.
+ * Be careful dealing with this extra offset as it is in app units of the
+ * parent document, which may have a different app units per dev pixel ratio
+ * than the child document.
+ */
+ static nsIFrame* GetCrossDocParentFrame(const nsIFrame* aFrame,
+ nsPoint* aCrossDocOffset = nullptr);
+
+ /**
+ * IsProperAncestorFrame checks whether aAncestorFrame is an ancestor
+ * of aFrame and not equal to aFrame.
+ * @param aCommonAncestor nullptr, or a common ancestor of aFrame and
+ * aAncestorFrame. If non-null, this can bound the search and speed up
+ * the function
+ */
+ static bool IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
+ nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * Like IsProperAncestorFrame, but looks across document boundaries.
+ *
+ * Just like IsAncestorFrameCrossDoc, except that it returns false when
+ * aFrame == aAncestorFrame.
+ */
+ static bool IsProperAncestorFrameCrossDoc(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
+ nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * IsAncestorFrameCrossDoc checks whether aAncestorFrame is an ancestor
+ * of aFrame or equal to aFrame, looking across document boundaries.
+ * @param aCommonAncestor nullptr, or a common ancestor of aFrame and
+ * aAncestorFrame. If non-null, this can bound the search and speed up
+ * the function.
+ *
+ * Just like IsProperAncestorFrameCrossDoc, except that it returns true when
+ * aFrame == aAncestorFrame.
+ */
+ static bool IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * Sets the fixed-pos metadata properties on aLayer.
+ * aAnchorRect is the basic anchor rectangle. If aFixedPosFrame is not a viewport
+ * frame, then we pick a corner of aAnchorRect to as the anchor point for the
+ * fixed-pos layer (i.e. the point to remain stable during zooming), based
+ * on which of the fixed-pos frame's CSS absolute positioning offset
+ * properties (top, left, right, bottom) are auto. aAnchorRect is in the
+ * coordinate space of aLayer's container layer (i.e. relative to the reference
+ * frame of the display item which is building aLayer's container layer).
+ */
+ static void SetFixedPositionLayerData(Layer* aLayer, const nsIFrame* aViewportFrame,
+ const nsRect& aAnchorRect,
+ const nsIFrame* aFixedPosFrame,
+ nsPresContext* aPresContext,
+ const ContainerLayerParameters& aContainerParameters);
+
+ /**
+ * 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);
+
+ /**
+ * Store whether aThumbFrame wants its own layer. This sets a property on
+ * the frame.
+ */
+ static void SetScrollbarThumbLayerization(nsIFrame* aThumbFrame, bool aLayerize);
+
+ /**
+ * Returns whether aThumbFrame wants its own layer due to having called
+ * SetScrollbarThumbLayerization.
+ */
+ static bool IsScrollbarThumbLayerized(nsIFrame* aThumbFrame);
+
+ /**
+ * GetScrollableFrameFor returns the scrollable frame for a scrolled frame
+ */
+ static nsIScrollableFrame* GetScrollableFrameFor(const nsIFrame *aScrolledFrame);
+
+ /**
+ * GetNearestScrollableFrameForDirection locates the first ancestor of
+ * aFrame (or aFrame itself) that is scrollable with overflow:scroll or
+ * overflow:auto in the given direction and where either the scrollbar for
+ * that direction is visible or the frame can be scrolled by some
+ * positive amount in that direction.
+ * The search extends across document boundaries.
+ *
+ * @param aFrame the frame to start with
+ * @param aDirection Whether it's for horizontal or vertical scrolling.
+ * @return the nearest scrollable frame or nullptr if not found
+ */
+ enum Direction { eHorizontal, eVertical };
+ static nsIScrollableFrame* GetNearestScrollableFrameForDirection(nsIFrame* aFrame,
+ Direction aDirection);
+
+ enum {
+ /**
+ * If the SCROLLABLE_SAME_DOC flag is set, then we only walk the frame tree
+ * up to the root frame in the current document.
+ */
+ SCROLLABLE_SAME_DOC = 0x01,
+ /**
+ * If the SCROLLABLE_INCLUDE_HIDDEN flag is set then we allow
+ * overflow:hidden scrollframes to be returned as scrollable frames.
+ */
+ SCROLLABLE_INCLUDE_HIDDEN = 0x02,
+ /**
+ * If the SCROLLABLE_ONLY_ASYNC_SCROLLABLE flag is set, then we only
+ * want to match scrollable frames for which WantAsyncScroll() returns
+ * true.
+ */
+ SCROLLABLE_ONLY_ASYNC_SCROLLABLE = 0x04,
+ /**
+ * If the SCROLLABLE_ALWAYS_MATCH_ROOT flag is set, then we will always
+ * return the root scrollable frame for the root document (in the current
+ * process) if we encounter it, whether or not it is async scrollable or
+ * overflow: hidden.
+ */
+ SCROLLABLE_ALWAYS_MATCH_ROOT = 0x08,
+ /**
+ * If the SCROLLABLE_FIXEDPOS_FINDS_ROOT flag is set, then for fixed-pos
+ * frames that are in the root document (in the current process) return the
+ * root scrollable frame for that document.
+ */
+ SCROLLABLE_FIXEDPOS_FINDS_ROOT = 0x10
+ };
+ /**
+ * GetNearestScrollableFrame locates the first ancestor of aFrame
+ * (or aFrame itself) that is scrollable with overflow:scroll or
+ * overflow:auto in some direction.
+ *
+ * @param aFrame the frame to start with
+ * @param aFlags if SCROLLABLE_SAME_DOC is set, do not search across
+ * document boundaries. If SCROLLABLE_INCLUDE_HIDDEN is set, include
+ * frames scrollable with overflow:hidden.
+ * @return the nearest scrollable frame or nullptr if not found
+ */
+ static nsIScrollableFrame* GetNearestScrollableFrame(nsIFrame* aFrame,
+ uint32_t aFlags = 0);
+
+ /**
+ * GetScrolledRect returns the range of allowable scroll offsets
+ * for aScrolledFrame, assuming the scrollable overflow area is
+ * aScrolledFrameOverflowArea and the scrollport size is aScrollPortSize.
+ * aDirection is either NS_STYLE_DIRECTION_LTR or NS_STYLE_DIRECTION_RTL.
+ */
+ static nsRect GetScrolledRect(nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledFrameOverflowArea,
+ const nsSize& aScrollPortSize,
+ uint8_t aDirection);
+
+ /**
+ * HasPseudoStyle returns true if aContent (whose primary style
+ * context is aStyleContext) has the aPseudoElement pseudo-style
+ * attached to it; returns false otherwise.
+ *
+ * @param aContent the content node we're looking at
+ * @param aStyleContext aContent's style context
+ * @param aPseudoElement the id of the pseudo style we care about
+ * @param aPresContext the presentation context
+ * @return whether aContent has aPseudoElement style attached to it
+ */
+ static bool HasPseudoStyle(nsIContent* aContent,
+ nsStyleContext* aStyleContext,
+ mozilla::CSSPseudoElementType aPseudoElement,
+ nsPresContext* aPresContext);
+
+ /**
+ * If this frame is a placeholder for a float, then return the float,
+ * otherwise return nullptr. aPlaceholder must be a placeholder frame.
+ */
+ static nsIFrame* GetFloatFromPlaceholder(nsIFrame* aPlaceholder);
+
+ // Combine aNewBreakType with aOrigBreakType, but limit the break types
+ // to StyleClear::Left, Right, Both.
+ static mozilla::StyleClear CombineBreakType(mozilla::StyleClear aOrigBreakType,
+ mozilla::StyleClear aNewBreakType);
+
+ /**
+ * Get the coordinates of a given DOM mouse event, relative to a given
+ * frame. Works only for DOM events generated by WidgetGUIEvents.
+ * @param aDOMEvent the event
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetDOMEventCoordinatesRelativeTo(nsIDOMEvent* aDOMEvent,
+ nsIFrame* aFrame);
+
+ /**
+ * Get the coordinates of a given native mouse event, relative to a given
+ * frame.
+ * @param aEvent the event
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(
+ const mozilla::WidgetEvent* aEvent,
+ nsIFrame* aFrame);
+
+ /**
+ * Get the coordinates of a given point relative to an event and a
+ * given frame.
+ * @param aEvent the event
+ * @param aPoint the point to get the coordinates relative to
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(
+ const mozilla::WidgetEvent* aEvent,
+ const mozilla::LayoutDeviceIntPoint& aPoint,
+ nsIFrame* aFrame);
+
+ /**
+ * Get the coordinates of a given point relative to a widget and a
+ * given frame.
+ * @param aWidget the event src widget
+ * @param aPoint the point to get the coordinates relative to
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
+ const mozilla::LayoutDeviceIntPoint& aPoint,
+ nsIFrame* aFrame);
+
+ /**
+ * Get the popup frame of a given native mouse event.
+ * @param aPresContext only check popups within aPresContext or a descendant
+ * @param aEvent the event.
+ * @return Null, if there is no popup frame at the point, otherwise,
+ * returns top-most popup frame at the point.
+ */
+ static nsIFrame* GetPopupFrameForEventCoordinates(
+ nsPresContext* aPresContext,
+ const mozilla::WidgetEvent* aEvent);
+
+ /**
+ * Translate from widget coordinates to the view's coordinates
+ * @param aPresContext the PresContext for the view
+ * @param aWidget the widget
+ * @param aPt the point relative to the widget
+ * @param aView view to which returned coordinates are relative
+ * @return the point in the view's coordinates
+ */
+ static nsPoint TranslateWidgetToView(nsPresContext* aPresContext,
+ nsIWidget* aWidget,
+ const mozilla::LayoutDeviceIntPoint& aPt,
+ nsView* aView);
+
+ /**
+ * Translate from view coordinates to the widget's coordinates.
+ * @param aPresContext the PresContext for the view
+ * @param aView the view
+ * @param aPt the point relative to the view
+ * @param aWidget the widget to which returned coordinates are relative
+ * @return the point in the view's coordinates
+ */
+ static mozilla::LayoutDeviceIntPoint
+ TranslateViewToWidget(nsPresContext* aPresContext,
+ nsView* aView, nsPoint aPt,
+ nsIWidget* aWidget);
+
+ enum FrameForPointFlags {
+ /**
+ * When set, paint suppression is ignored, so we'll return non-root page
+ * elements even if paint suppression is stopping them from painting.
+ */
+ IGNORE_PAINT_SUPPRESSION = 0x01,
+ /**
+ * When set, clipping due to the root scroll frame (and any other viewport-
+ * related clipping) is ignored.
+ */
+ IGNORE_ROOT_SCROLL_FRAME = 0x02,
+ /**
+ * When set, return only content in the same document as aFrame.
+ */
+ IGNORE_CROSS_DOC = 0x04
+ };
+
+ /**
+ * Given aFrame, the root frame of a stacking context, find its descendant
+ * frame under the point aPt that receives a mouse event at that location,
+ * or nullptr if there is no such frame.
+ * @param aPt the point, relative to the frame origin
+ * @param aFlags some combination of FrameForPointFlags
+ */
+ static nsIFrame* GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
+ uint32_t aFlags = 0);
+
+ /**
+ * Given aFrame, the root frame of a stacking context, find all descendant
+ * frames under the area of a rectangle that receives a mouse event,
+ * or nullptr if there is no such frame.
+ * @param aRect the rect, relative to the frame origin
+ * @param aOutFrames an array to add all the frames found
+ * @param aFlags some combination of FrameForPointFlags
+ */
+ static nsresult GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
+ nsTArray<nsIFrame*> &aOutFrames,
+ uint32_t aFlags = 0);
+
+ /**
+ * Transform aRect relative to aFrame up to the coordinate system of
+ * aAncestor. Computes the bounding-box of the true quadrilateral.
+ * Pass non-null aPreservesAxisAlignedRectangles and it will be set to true if
+ * we only need to use a 2d transform that PreservesAxisAlignedRectangles().
+ *
+ * |aMatrixCache| allows for optimizations in recomputing the same matrix over
+ * and over. The argument can be one of the following values:
+ * nullptr (the default) - No optimization; the transform matrix is computed on
+ * every call to this function.
+ * non-null pointer to an empty Maybe<Matrix4x4> - Upon return, the Maybe is
+ * filled with the transform matrix that was computed. This can then be passed
+ * in to subsequent calls with the same source and destination frames to avoid
+ * recomputing the matrix.
+ * non-null pointer to a non-empty Matrix4x4 - The provided matrix will be used
+ * as the transform matrix and applied to the rect.
+ */
+ static nsRect TransformFrameRectToAncestor(nsIFrame* aFrame,
+ const nsRect& aRect,
+ const nsIFrame* aAncestor,
+ bool* aPreservesAxisAlignedRectangles = nullptr,
+ mozilla::Maybe<Matrix4x4>* aMatrixCache = nullptr);
+
+
+ /**
+ * Gets the transform for aFrame relative to aAncestor. Pass null for
+ * aAncestor to go up to the root frame.
+ */
+ static Matrix4x4 GetTransformToAncestor(nsIFrame *aFrame, const nsIFrame *aAncestor);
+
+ /**
+ * Gets the scale factors of the transform for aFrame relative to the root
+ * frame if this transform is 2D, or the identity scale factors otherwise.
+ */
+ static gfxSize GetTransformToAncestorScale(nsIFrame* aFrame);
+
+ /**
+ * Gets the scale factors of the transform for aFrame relative to the root
+ * frame if this transform is 2D, or the identity scale factors otherwise.
+ * If some frame on the path from aFrame to the display root frame may have an
+ * animated scale, returns the identity scale factors.
+ */
+ static gfxSize GetTransformToAncestorScaleExcludingAnimated(nsIFrame* aFrame);
+
+ /**
+ * Find the nearest common ancestor frame for aFrame1 and aFrame2. The
+ * ancestor frame could be cross-doc.
+ */
+ static nsIFrame* FindNearestCommonAncestorFrame(nsIFrame* aFrame1,
+ nsIFrame* aFrame2);
+
+ /**
+ * Transforms a list of CSSPoints from aFromFrame to aToFrame, taking into
+ * account all relevant transformations on the frames up to (but excluding)
+ * their nearest common ancestor.
+ * If we encounter a transform that we need to invert but which is
+ * non-invertible, we return NONINVERTIBLE_TRANSFORM. If the frames have
+ * no common ancestor, we return NO_COMMON_ANCESTOR.
+ * If this returns TRANSFORM_SUCCEEDED, the points in aPoints are transformed
+ * in-place, otherwise they are untouched.
+ */
+ enum TransformResult {
+ TRANSFORM_SUCCEEDED,
+ NO_COMMON_ANCESTOR,
+ NONINVERTIBLE_TRANSFORM
+ };
+ static TransformResult TransformPoints(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ uint32_t aPointCount, CSSPoint* aPoints);
+
+ /**
+ * Same as above function, but transform points in app units and
+ * handle 1 point per call.
+ */
+ static TransformResult TransformPoint(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ nsPoint& aPoint);
+
+ /**
+ * Transforms a rect from aFromFrame to aToFrame. In app units.
+ * Returns the bounds of the actual rect if the transform requires rotation
+ * or anything complex like that.
+ */
+ static TransformResult TransformRect(nsIFrame* aFromFrame, nsIFrame* aToFrame,
+ nsRect& aRect);
+
+ /**
+ * Converts app units to pixels (with optional snapping) and appends as a
+ * translation to aTransform.
+ */
+ static void PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin, float aAppUnitsPerPixel, bool aRounded);
+
+ /**
+ * Get the border-box of aElement's primary frame, transformed it to be
+ * relative to aFrame.
+ */
+ static nsRect GetRectRelativeToFrame(mozilla::dom::Element* aElement,
+ nsIFrame* aFrame);
+
+ /**
+ * Returns true if aRect with border inflation of size aInflateSize contains
+ * aPoint.
+ */
+ static bool ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
+ nscoord aInflateSize);
+
+ /**
+ * Clamp aRect relative to aFrame to the scroll frames boundary searching from
+ * aFrame.
+ */
+ static nsRect ClampRectToScrollFrames(nsIFrame* aFrame,
+ const nsRect& aRect);
+
+ /**
+ * Return true if a "layer transform" could be computed for aFrame,
+ * and optionally return the computed transform. The returned
+ * transform is what would be set on the layer currently if a layers
+ * transaction were opened at the time this helper is called.
+ */
+ static bool GetLayerTransformForFrame(nsIFrame* aFrame,
+ Matrix4x4* aTransform);
+
+ /**
+ * Given a point in the global coordinate space, returns that point expressed
+ * in the coordinate system of aFrame. This effectively inverts all
+ * transforms between this point and the root frame.
+ *
+ * @param aFrame The frame that acts as the coordinate space container.
+ * @param aPoint The point, in the global space, to get in the frame-local space.
+ * @return aPoint, expressed in aFrame's canonical coordinate space.
+ */
+ static nsPoint TransformRootPointToFrame(nsIFrame* aFrame,
+ const nsPoint &aPoint)
+ {
+ return TransformAncestorPointToFrame(aFrame, aPoint, nullptr);
+ }
+
+ /**
+ * Transform aPoint relative to aAncestor down to the coordinate system of
+ * aFrame.
+ */
+ static nsPoint TransformAncestorPointToFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ nsIFrame* aAncestor);
+
+ /**
+ * Helper function that, given a rectangle and a matrix, returns the smallest
+ * rectangle containing the image of the source rectangle.
+ *
+ * @param aBounds The rectangle to transform.
+ * @param aMatrix The matrix to transform it with.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The smallest rect that contains the image of aBounds.
+ */
+ static nsRect MatrixTransformRect(const nsRect &aBounds,
+ const Matrix4x4 &aMatrix, float aFactor);
+
+ /**
+ * Helper function that, given a point and a matrix, returns the image
+ * of that point under the matrix transform.
+ *
+ * @param aPoint The point to transform.
+ * @param aMatrix The matrix to transform it with.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The image of the point under the transform.
+ */
+ static nsPoint MatrixTransformPoint(const nsPoint &aPoint,
+ const Matrix4x4 &aMatrix, float aFactor);
+
+ /**
+ * Given a graphics rectangle in graphics space, return a rectangle in
+ * app space that contains the graphics rectangle, rounding out as necessary.
+ *
+ * @param aRect The graphics rect to round outward.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The smallest rectangle in app space that contains aRect.
+ */
+ static nsRect RoundGfxRectToAppRect(const Rect &aRect, float aFactor);
+
+ /**
+ * Given a graphics rectangle in graphics space, return a rectangle in
+ * app space that contains the graphics rectangle, rounding out as necessary.
+ *
+ * @param aRect The graphics rect to round outward.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The smallest rectangle in app space that contains aRect.
+ */
+ static nsRect RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor);
+
+ /**
+ * Returns a subrectangle of aContainedRect that is entirely inside the rounded
+ * rect. Complex cases are handled conservatively by returning a smaller
+ * rect than necessary.
+ */
+ static nsRegion RoundedRectIntersectRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect);
+ static nsIntRegion RoundedRectIntersectIntRect(const nsIntRect& aRoundedRect,
+ const RectCornerRadii& aCornerRadii,
+ const nsIntRect& aContainedRect);
+
+ /**
+ * Return whether any part of aTestRect is inside of the rounded
+ * rectangle formed by aBounds and aRadii (which are indexed by the
+ * NS_CORNER_* constants in nsStyleConsts.h). This is precise.
+ */
+ static bool RoundedRectIntersectsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aTestRect);
+
+ enum class PaintFrameFlags : uint32_t {
+ PAINT_IN_TRANSFORM = 0x01,
+ PAINT_SYNC_DECODE_IMAGES = 0x02,
+ PAINT_WIDGET_LAYERS = 0x04,
+ PAINT_IGNORE_SUPPRESSION = 0x08,
+ PAINT_DOCUMENT_RELATIVE = 0x10,
+ PAINT_HIDE_CARET = 0x20,
+ PAINT_TO_WINDOW = 0x40,
+ PAINT_EXISTING_TRANSACTION = 0x80,
+ PAINT_NO_COMPOSITE = 0x100,
+ PAINT_COMPRESSED = 0x200
+ };
+
+ /**
+ * Given aFrame, the root frame of a stacking context, paint it and its
+ * descendants to aRenderingContext.
+ * @param aRenderingContext a rendering context translated so that (0,0)
+ * is the origin of aFrame; for best results, (0,0) should transform
+ * to pixel-aligned coordinates. This can be null, in which case
+ * aFrame must be a "display root" (root frame for a root document,
+ * or the root of a popup) with an associated widget and we draw using
+ * the layer manager for the frame's widget.
+ * @param aDirtyRegion the region that must be painted, in the coordinates
+ * of aFrame.
+ * @param aBackstop paint the dirty area with this color before drawing
+ * the actual content; pass NS_RGBA(0,0,0,0) to draw no background.
+ * @param aBuilderMode Passed through to the display-list builder.
+ * @param aFlags if PAINT_IN_TRANSFORM is set, then we assume
+ * this is inside a transform or SVG foreignObject. If
+ * PAINT_SYNC_DECODE_IMAGES is set, we force synchronous decode on all
+ * images. If PAINT_WIDGET_LAYERS is set, aFrame must be a display root,
+ * and we will use the frame's widget's layer manager to paint
+ * even if aRenderingContext is non-null. This is useful if you want
+ * to force rendering to use the widget's layer manager for testing
+ * or speed. PAINT_WIDGET_LAYERS must be set if aRenderingContext is null.
+ * If PAINT_DOCUMENT_RELATIVE is used, the visible region is interpreted
+ * as being relative to the document (normally it's relative to the CSS
+ * viewport) and the document is painted as if no scrolling has occured.
+ * Only considered if nsIPresShell::IgnoringViewportScrolling is true.
+ * PAINT_TO_WINDOW sets painting to window to true on the display list
+ * builder even if we can't tell that we are painting to the window.
+ * If PAINT_EXISTING_TRANSACTION is set, then BeginTransaction() has already
+ * been called on aFrame's widget's layer manager and should not be
+ * called again.
+ * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to
+ * compressed mode to avoid short cut optimizations.
+ *
+ * So there are three possible behaviours:
+ * 1) PAINT_WIDGET_LAYERS is set and aRenderingContext is null; we paint
+ * by calling BeginTransaction on the widget's layer manager.
+ * 2) PAINT_WIDGET_LAYERS is set and aRenderingContext is non-null; we
+ * paint by calling BeginTransactionWithTarget on the widget's layer
+ * manager.
+ * 3) PAINT_WIDGET_LAYERS is not set and aRenderingContext is non-null;
+ * we paint by construct a BasicLayerManager and calling
+ * BeginTransactionWithTarget on it. This is desirable if we're doing
+ * something like drawWindow in a mode where what gets rendered doesn't
+ * necessarily correspond to what's visible in the window; we don't
+ * want to mess up the widget's layer tree.
+ */
+ static nsresult PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame,
+ const nsRegion& aDirtyRegion, nscolor aBackstop,
+ nsDisplayListBuilderMode aBuilderMode,
+ PaintFrameFlags aFlags = PaintFrameFlags(0));
+
+ /**
+ * Uses a binary search for find where the cursor falls in the line of text
+ * It also keeps track of the part of the string that has already been
+ * measured so it doesn't have to keep measuring the same text over and over.
+ *
+ * @param "aBaseWidth" contains the width in twips of the portion
+ * of the text that has already been measured, and aBaseInx contains
+ * the index of the text that has already been measured.
+ *
+ * @param aTextWidth returns (in twips) the length of the text that falls
+ * before the cursor aIndex contains the index of the text where the cursor
+ * falls.
+ */
+ static bool
+ BinarySearchForPosition(DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ const char16_t* aText,
+ int32_t aBaseWidth,
+ int32_t aBaseInx,
+ int32_t aStartInx,
+ int32_t aEndInx,
+ int32_t aCursorPos,
+ int32_t& aIndex,
+ int32_t& aTextWidth);
+
+ class BoxCallback {
+ public:
+ BoxCallback() : mIncludeCaptionBoxForTable(true) {}
+ virtual void AddBox(nsIFrame* aFrame) = 0;
+ bool mIncludeCaptionBoxForTable;
+ };
+ /**
+ * Collect all CSS boxes associated with aFrame and its
+ * continuations, "drilling down" through table wrapper frames and
+ * some anonymous blocks since they're not real CSS boxes.
+ * If aFrame is null, no boxes are returned.
+ * SVG frames return a single box, themselves.
+ */
+ static void GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback);
+
+ /**
+ * Find the first frame descendant of aFrame (including aFrame) which is
+ * not an anonymous frame that getBoxQuads/getClientRects should ignore.
+ */
+ static nsIFrame* GetFirstNonAnonymousFrame(nsIFrame* aFrame);
+
+ class RectCallback {
+ public:
+ virtual void AddRect(const nsRect& aRect) = 0;
+ };
+
+ struct RectAccumulator : public RectCallback {
+ nsRect mResultRect;
+ nsRect mFirstRect;
+ bool mSeenFirstRect;
+
+ RectAccumulator();
+
+ virtual void AddRect(const nsRect& aRect) override;
+ };
+
+ struct RectListBuilder : public RectCallback {
+ DOMRectList* mRectList;
+
+ explicit RectListBuilder(DOMRectList* aList);
+ virtual void AddRect(const nsRect& aRect) override;
+ };
+
+ static nsIFrame* GetContainingBlockForClientRect(nsIFrame* aFrame);
+
+ enum {
+ RECTS_ACCOUNT_FOR_TRANSFORMS = 0x01,
+ // Two bits for specifying which box type to use.
+ // With neither bit set (default), use the border box.
+ RECTS_USE_CONTENT_BOX = 0x02,
+ RECTS_USE_PADDING_BOX = 0x04,
+ RECTS_USE_MARGIN_BOX = 0x06, // both bits set
+ RECTS_WHICH_BOX_MASK = 0x06 // bitmask for these two bits
+ };
+ /**
+ * Collect all CSS boxes (content, padding, border, or margin) associated
+ * with aFrame and its continuations, "drilling down" through table wrapper
+ * frames and some anonymous blocks since they're not real CSS boxes.
+ * The boxes are positioned relative to aRelativeTo (taking scrolling
+ * into account) and passed to the callback in frame-tree order.
+ * If aFrame is null, no boxes are returned.
+ * For SVG frames, returns one rectangle, the bounding box.
+ * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
+ * the boxes into aRelativeTo coordinates, transforms (including CSS
+ * and SVG transforms) are taken into account.
+ * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
+ * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
+ * Otherwise (by default), the border box is used.
+ */
+ static void GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ RectCallback* aCallback, uint32_t aFlags = 0);
+
+ static void GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ RectCallback* aCallback,
+ mozilla::dom::DOMStringList* aTextList,
+ uint32_t aFlags = 0);
+
+ /**
+ * Computes the union of all rects returned by GetAllInFlowRects. If
+ * the union is empty, returns the first rect.
+ * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
+ * the boxes into aRelativeTo coordinates, transforms (including CSS
+ * and SVG transforms) are taken into account.
+ * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
+ * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
+ * Otherwise (by default), the border box is used.
+ */
+ static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+ uint32_t aFlags = 0);
+
+ enum {
+ EXCLUDE_BLUR_SHADOWS = 0x01
+ };
+ /**
+ * Takes a text-shadow array from the style properties of a given nsIFrame and
+ * computes the union of those shadows along with the given initial rect.
+ * If there are no shadows, the initial rect is returned.
+ */
+ static nsRect GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
+ nsIFrame* aFrame,
+ uint32_t aFlags = 0);
+
+ /**
+ * Computes the destination rect that a given replaced element should render
+ * into, based on its CSS 'object-fit' and 'object-position' properties.
+ *
+ * @param aConstraintRect The constraint rect that we have at our disposal,
+ * which would e.g. be exactly filled by the image
+ * if we had "object-fit: fill".
+ * @param aIntrinsicSize The replaced content's intrinsic size, as reported
+ * by nsIFrame::GetIntrinsicSize().
+ * @param aIntrinsicRatio The replaced content's intrinsic ratio, as reported
+ * by nsIFrame::GetIntrinsicRatio().
+ * @param aStylePos The nsStylePosition struct that contains the 'object-fit'
+ * and 'object-position' values that we should rely on.
+ * (This should usually be the nsStylePosition for the
+ * replaced element in question, but not always. For
+ * example, a <video>'s poster-image has a dedicated
+ * anonymous element & child-frame, but we should still use
+ * the <video>'s 'object-fit' and 'object-position' values.)
+ * @param aAnchorPoint [out] A point that should be pixel-aligned by functions
+ * like nsLayoutUtils::DrawImage. See documentation
+ * in nsCSSRendering.h for ComputeObjectAnchorPoint.
+ * @return The nsRect into which we should render the replaced content (using
+ * the same coordinate space as the passed-in aConstraintRect).
+ */
+ static nsRect ComputeObjectDestRect(const nsRect& aConstraintRect,
+ const IntrinsicSize& aIntrinsicSize,
+ const nsSize& aIntrinsicRatio,
+ const nsStylePosition* aStylePos,
+ nsPoint* aAnchorPoint = nullptr);
+
+ /**
+ * Get the font metrics corresponding to the frame's style data.
+ * @param aFrame the frame
+ * @param aSizeInflation number to multiply font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsForFrame(
+ const nsIFrame* aFrame, float aSizeInflation);
+
+ static already_AddRefed<nsFontMetrics>
+ GetInflatedFontMetricsForFrame(const nsIFrame* aFrame)
+ {
+ return GetFontMetricsForFrame(aFrame, FontSizeInflationFor(aFrame));
+ }
+
+ /**
+ * Get the font metrics corresponding to the given style data.
+ * @param aStyleContext the style data
+ * @param aSizeInflation number to multiply font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsForStyleContext(
+ nsStyleContext* aStyleContext, float aSizeInflation = 1.0f,
+ uint8_t aVariantWidth = NS_FONT_VARIANT_WIDTH_NORMAL);
+
+ /**
+ * Get the font metrics of emphasis marks corresponding to the given
+ * style data. The result is same as GetFontMetricsForStyleContext
+ * except that the font size is scaled down to 50%.
+ * @param aStyleContext the style data
+ * @param aInflation number to multiple font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsOfEmphasisMarks(
+ nsStyleContext* aStyleContext, float aInflation)
+ {
+ return GetFontMetricsForStyleContext(aStyleContext, aInflation * 0.5f);
+ }
+
+ /**
+ * Find the immediate child of aParent whose frame subtree contains
+ * aDescendantFrame. Returns null if aDescendantFrame is not a descendant
+ * of aParent.
+ */
+ static nsIFrame* FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame);
+
+ /**
+ * Find the nearest ancestor that's a block
+ */
+ static nsBlockFrame* FindNearestBlockAncestor(nsIFrame* aFrame);
+
+ /**
+ * Find the nearest ancestor that's not for generated content. Will return
+ * aFrame if aFrame is not for generated content.
+ */
+ static nsIFrame* GetNonGeneratedAncestor(nsIFrame* aFrame);
+
+ /**
+ * Cast aFrame to an nsBlockFrame* or return null if it's not
+ * an nsBlockFrame.
+ */
+ static nsBlockFrame* GetAsBlock(nsIFrame* aFrame);
+
+ /*
+ * Whether the frame is an nsBlockFrame which is not a wrapper block.
+ */
+ static bool IsNonWrapperBlock(nsIFrame* aFrame);
+
+ /**
+ * If aFrame is an out of flow frame, return its placeholder, otherwise
+ * return its parent.
+ */
+ static nsIFrame* GetParentOrPlaceholderFor(nsIFrame* aFrame);
+
+ /**
+ * If aFrame is an out of flow frame, return its placeholder, otherwise
+ * return its (possibly cross-doc) parent.
+ */
+ static nsIFrame* GetParentOrPlaceholderForCrossDoc(nsIFrame* aFrame);
+
+ /**
+ * Get a frame's next-in-flow, or, if it doesn't have one, its
+ * block-in-inline-split sibling.
+ */
+ static nsIFrame*
+ GetNextContinuationOrIBSplitSibling(nsIFrame *aFrame);
+
+ /**
+ * Get the first frame in the continuation-plus-ib-split-sibling chain
+ * containing aFrame.
+ */
+ static nsIFrame*
+ FirstContinuationOrIBSplitSibling(nsIFrame *aFrame);
+
+ /**
+ * Get the last frame in the continuation-plus-ib-split-sibling chain
+ * containing aFrame.
+ */
+ static nsIFrame*
+ LastContinuationOrIBSplitSibling(nsIFrame *aFrame);
+
+ /**
+ * Is FirstContinuationOrIBSplitSibling(aFrame) going to return
+ * aFrame?
+ */
+ static bool
+ IsFirstContinuationOrIBSplitSibling(nsIFrame *aFrame);
+
+ /**
+ * Check whether aFrame is a part of the scrollbar or scrollcorner of
+ * the root content.
+ * @param aFrame the checking frame.
+ * @return true if the frame is a part of the scrollbar or scrollcorner of
+ * the root content.
+ */
+ static bool IsViewportScrollbarFrame(nsIFrame* aFrame);
+
+ /**
+ * Get the contribution of aFrame to its containing block's intrinsic
+ * size for the given physical axis. This considers the child's intrinsic
+ * width, its 'width', 'min-width', and 'max-width' properties (or 'height'
+ * variations if that's what matches aAxis) and its padding, border and margin
+ * in the corresponding dimension.
+ * @param aMarginBoxMinSizeClamp make the result fit within this margin-box
+ * size by reducing the *content size* (flooring at zero). This is used for:
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ */
+ enum class IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE };
+ static const auto MIN_ISIZE = IntrinsicISizeType::MIN_ISIZE;
+ static const auto PREF_ISIZE = IntrinsicISizeType::PREF_ISIZE;
+ enum {
+ IGNORE_PADDING = 0x01,
+ BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_WIDTH_UNKNOWN if so
+ MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height
+ ADD_PERCENTS = 0x08, // apply AddPercents also for MIN_ISIZE
+ };
+ static nscoord
+ IntrinsicForAxis(mozilla::PhysicalAxis aAxis,
+ nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags = 0,
+ nscoord aMarginBoxMinSizeClamp = NS_MAXSIZE);
+ /**
+ * Calls IntrinsicForAxis with aFrame's parent's inline physical axis.
+ */
+ static nscoord IntrinsicForContainer(nsRenderingContext* aRenderingContext,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags = 0);
+
+ /**
+ * Get the definite size contribution of aFrame for the given physical axis.
+ * This considers the child's 'min-width' property (or 'min-height' if the
+ * given axis is vertical), and its padding, border, and margin in the
+ * corresponding dimension. If the 'min-' property is 'auto' (and 'overflow'
+ * is 'visible') and the corresponding 'width'/'height' is definite it returns
+ * the "specified size" for:
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ * Note that the "transferred size" is not handled here; use IntrinsicForAxis.
+ * Note that any percentage in 'width'/'height' makes it count as indefinite.
+ * If the 'min-' property is 'auto' and 'overflow' is not 'visible', then it
+ * calculates the result as if the 'min-' computed value is zero.
+ * Otherwise, return NS_UNCONSTRAINEDSIZE.
+ *
+ * @note this behavior is specific to Grid/Flexbox (currently) so aFrame
+ * should be a grid/flex item.
+ */
+ static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis,
+ nsRenderingContext* aRC,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags = 0);
+
+ /**
+ * This function increases an initial intrinsic size, 'aCurrent', according
+ * to the given 'aPercent', such that the size-increase makes up exactly
+ * 'aPercent' percent of the returned value. If 'aPercent' is less than
+ * or equal to zero the original 'aCurrent' value is returned. If 'aPercent'
+ * is greater than or equal to 1.0 the value nscoord_MAX is returned.
+ */
+ static nscoord AddPercents(nscoord aCurrent, float aPercent)
+ {
+ if (aPercent > 0.0f) {
+ return MOZ_UNLIKELY(aPercent >= 1.0f) ? nscoord_MAX
+ : NSToCoordRound(float(aCurrent) / (1.0f - aPercent));
+ }
+ return aCurrent;
+ }
+
+ /*
+ * Convert nsStyleCoord to nscoord when percentages depend on the
+ * containing block size.
+ * @param aPercentBasis The width or height of the containing block
+ * (whichever the client wants to use for resolving percentages).
+ */
+ static nscoord ComputeCBDependentValue(nscoord aPercentBasis,
+ const nsStyleCoord& aCoord);
+
+ static nscoord ComputeBSizeDependentValue(
+ nscoord aContainingBlockBSize,
+ const nsStyleCoord& aCoord);
+
+ static nscoord ComputeBSizeValue(nscoord aContainingBlockBSize,
+ nscoord aContentEdgeToBoxSizingBoxEdge,
+ const nsStyleCoord& aCoord)
+ {
+ MOZ_ASSERT(aContainingBlockBSize != nscoord_MAX || !aCoord.HasPercent(),
+ "caller must deal with %% of unconstrained block-size");
+ MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit());
+
+ nscoord result =
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, aContainingBlockBSize);
+ // Clamp calc(), and the subtraction for box-sizing.
+ return std::max(0, result - aContentEdgeToBoxSizingBoxEdge);
+ }
+
+ // XXX to be removed
+ static bool IsAutoHeight(const nsStyleCoord &aCoord, nscoord aCBHeight)
+ {
+ return IsAutoBSize(aCoord, aCBHeight);
+ }
+
+ static bool IsAutoBSize(const nsStyleCoord &aCoord, nscoord aCBBSize)
+ {
+ nsStyleUnit unit = aCoord.GetUnit();
+ return unit == eStyleUnit_Auto || // only for 'height'
+ unit == eStyleUnit_None || // only for 'max-height'
+ // The enumerated values were originally aimed at inline-size
+ // (or width, as it was before logicalization). For now, let them
+ // return true here, so that we don't call ComputeBSizeValue with
+ // value types that it doesn't understand. (See bug 1113216.)
+ //
+ // FIXME (bug 567039, bug 527285)
+ // This isn't correct for the 'fill' value or for the 'min-*' or
+ // 'max-*' properties, which need to be handled differently by
+ // the callers of IsAutoBSize().
+ unit == eStyleUnit_Enumerated ||
+ (aCBBSize == nscoord_MAX && aCoord.HasPercent());
+ }
+
+ static bool IsPaddingZero(const nsStyleCoord &aCoord)
+ {
+ return (aCoord.GetUnit() == eStyleUnit_Coord &&
+ aCoord.GetCoordValue() == 0) ||
+ (aCoord.GetUnit() == eStyleUnit_Percent &&
+ aCoord.GetPercentValue() == 0.0f) ||
+ (aCoord.IsCalcUnit() &&
+ // clamp negative calc() to 0
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) <= 0 &&
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) <= 0);
+ }
+
+ static bool IsMarginZero(const nsStyleCoord &aCoord)
+ {
+ return (aCoord.GetUnit() == eStyleUnit_Coord &&
+ aCoord.GetCoordValue() == 0) ||
+ (aCoord.GetUnit() == eStyleUnit_Percent &&
+ aCoord.GetPercentValue() == 0.0f) ||
+ (aCoord.IsCalcUnit() &&
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) == 0 &&
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) == 0);
+ }
+
+ static void MarkDescendantsDirty(nsIFrame *aSubtreeRoot);
+
+ static void MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame* aFrame);
+
+ /*
+ * Calculate the used values for 'width' and 'height' when width
+ * and height are 'auto'. The tentWidth and tentHeight arguments should be
+ * the result of applying the rules for computing intrinsic sizes and ratios.
+ * as specified by CSS 2.1 sections 10.3.2 and 10.6.2
+ */
+ static nsSize ComputeAutoSizeWithIntrinsicDimensions(nscoord minWidth, nscoord minHeight,
+ nscoord maxWidth, nscoord maxHeight,
+ nscoord tentWidth, nscoord tentHeight);
+
+ // Implement nsIFrame::GetPrefISize in terms of nsIFrame::AddInlinePrefISize
+ static nscoord PrefISizeFromInline(nsIFrame* aFrame,
+ nsRenderingContext* aRenderingContext);
+
+ // Implement nsIFrame::GetMinISize in terms of nsIFrame::AddInlineMinISize
+ static nscoord MinISizeFromInline(nsIFrame* aFrame,
+ nsRenderingContext* aRenderingContext);
+
+ // Get a suitable foreground color for painting aProperty for aFrame.
+ static nscolor GetColor(nsIFrame* aFrame, nsCSSPropertyID aProperty);
+
+ // Get a baseline y position in app units that is snapped to device pixels.
+ static gfxFloat GetSnappedBaselineY(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aY, nscoord aAscent);
+ // Ditto for an x position (for vertical text). Note that for vertical-rl
+ // writing mode, the ascent value should be negated by the caller.
+ static gfxFloat GetSnappedBaselineX(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aX, nscoord aAscent);
+
+ static nscoord AppUnitWidthOfString(char16_t aC,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ return AppUnitWidthOfString(&aC, 1, aFontMetrics, aDrawTarget);
+ }
+ static nscoord AppUnitWidthOfString(const nsString& aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ return nsLayoutUtils::AppUnitWidthOfString(aString.get(), aString.Length(),
+ aFontMetrics, aDrawTarget);
+ }
+ static nscoord AppUnitWidthOfString(const char16_t *aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget);
+ static nscoord AppUnitWidthOfStringBidi(const nsString& aString,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext& aContext) {
+ return nsLayoutUtils::AppUnitWidthOfStringBidi(aString.get(),
+ aString.Length(), aFrame,
+ aFontMetrics, aContext);
+ }
+ static nscoord AppUnitWidthOfStringBidi(const char16_t* aString,
+ uint32_t aLength,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext& aContext);
+
+ static bool StringWidthIsGreaterThan(const nsString& aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget,
+ nscoord aWidth);
+
+ static nsBoundingMetrics AppUnitBoundsOfString(const char16_t* aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget);
+
+ static void DrawString(const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext* aContext,
+ const char16_t* aString,
+ int32_t aLength,
+ nsPoint aPoint,
+ nsStyleContext* aStyleContext = nullptr);
+
+ /**
+ * Supports only LTR or RTL. Bidi (mixed direction) is not supported.
+ */
+ static void DrawUniDirString(const char16_t* aString,
+ uint32_t aLength,
+ nsPoint aPoint,
+ nsFontMetrics& aFontMetrics,
+ nsRenderingContext& aContext);
+
+ /**
+ * Helper function for drawing text-shadow. The callback's job
+ * is to draw whatever needs to be blurred onto the given context.
+ */
+ typedef void (* TextShadowCallback)(nsRenderingContext* aCtx,
+ nsPoint aShadowOffset,
+ const nscolor& aShadowColor,
+ void* aData);
+
+ static void PaintTextShadow(const nsIFrame* aFrame,
+ nsRenderingContext* aContext,
+ const nsRect& aTextRect,
+ const nsRect& aDirtyRect,
+ const nscolor& aForegroundColor,
+ TextShadowCallback aCallback,
+ void* aCallbackData);
+
+ /**
+ * Gets the baseline to vertically center text from a font within a
+ * line of specified height.
+ * aIsInverted: true if the text is inverted relative to the block
+ * direction, so that the block-dir "ascent" corresponds to font
+ * descent. (Applies to sideways text in vertical-lr mode.)
+ *
+ * Returns the baseline position relative to the top of the line.
+ */
+ static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
+ nscoord aLineHeight,
+ bool aIsInverted);
+
+ /**
+ * Derive a baseline of |aFrame| (measured from its top border edge)
+ * from its first in-flow line box (not descending into anything with
+ * 'overflow' not 'visible', potentially including aFrame itself).
+ *
+ * Returns true if a baseline was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ static bool GetFirstLineBaseline(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame, nscoord* aResult);
+
+ /**
+ * Just like GetFirstLineBaseline, except also returns the top and
+ * bottom of the line with the baseline.
+ *
+ * Returns true if a line was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ struct LinePosition {
+ nscoord mBStart, mBaseline, mBEnd;
+
+ LinePosition operator+(nscoord aOffset) const {
+ LinePosition result;
+ result.mBStart = mBStart + aOffset;
+ result.mBaseline = mBaseline + aOffset;
+ result.mBEnd = mBEnd + aOffset;
+ return result;
+ }
+ };
+ static bool GetFirstLinePosition(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame,
+ LinePosition* aResult);
+
+
+ /**
+ * Derive a baseline of |aFrame| (measured from its top border edge)
+ * from its last in-flow line box (not descending into anything with
+ * 'overflow' not 'visible', potentially including aFrame itself).
+ *
+ * Returns true if a baseline was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ static bool GetLastLineBaseline(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame, nscoord* aResult);
+
+ /**
+ * Returns a block-dir coordinate relative to this frame's origin that
+ * represents the logical block-end of the frame or its visible content,
+ * whichever is further from the origin.
+ * Relative positioning is ignored and margins and glyph bounds are not
+ * considered.
+ * This value will be >= mRect.BSize() and <= overflowRect.BEnd() unless
+ * relative positioning is applied.
+ */
+ static nscoord CalculateContentBEnd(mozilla::WritingMode aWritingMode,
+ nsIFrame* aFrame);
+
+ /**
+ * Gets the closest frame (the frame passed in or one of its parents) that
+ * qualifies as a "layer"; used in DOM0 methods that depends upon that
+ * definition. This is the nearest frame that is either positioned or scrolled
+ * (the child of a scroll frame).
+ */
+ static nsIFrame* GetClosestLayer(nsIFrame* aFrame);
+
+ /**
+ * Gets the graphics sampling filter for the frame
+ */
+ static SamplingFilter GetSamplingFilterForFrame(nsIFrame* aFrame);
+
+ /* N.B. The only difference between variants of the Draw*Image
+ * functions below is the type of the aImage argument.
+ */
+
+ /**
+ * Draw a background image. The image's dimensions are as specified in aDest;
+ * the image itself is not consulted to determine a size.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aImageSize The unscaled size of the image being drawn.
+ * (This might be the image's size if no scaling
+ * occurs, or it might be the image's size if
+ * the image is a vector image being rendered at
+ * that size.)
+ * @param aDest The position and scaled area where one copy of
+ * the image should be drawn. This area represents
+ * the image itself in its correct position as defined
+ * with the background-position css property.
+ * @param aFill The area to be filled with copies of the image.
+ * @param aRepeatSize The distance between the positions of two subsequent
+ * repeats of the image. Sizes larger than aDest.Size()
+ * create gaps between the images.
+ * @param aAnchor A point in aFill which we will ensure is
+ * pixel-aligned in the output.
+ * @param aDirty Pixels outside this area may be skipped.
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety.
+ * @param aExtendMode How to extend the image over the dest rect.
+ */
+ static DrawResult DrawBackgroundImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const CSSIntSize& aImageSize,
+ SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsSize& aRepeatSize,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ uint32_t aImageFlags,
+ ExtendMode aExtendMode);
+
+ /**
+ * Draw an image.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aDest Where one copy of the image should mapped to.
+ * @param aFill The area to be filled with copies of the
+ * image.
+ * @param aAnchor A point in aFill which we will ensure is
+ * pixel-aligned in the output.
+ * @param aDirty Pixels outside this area may be skipped.
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety
+ */
+ static DrawResult DrawImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aFill,
+ const nsPoint& aAnchor,
+ const nsRect& aDirty,
+ uint32_t aImageFlags);
+
+ static inline void InitDashPattern(StrokeOptions& aStrokeOptions,
+ uint8_t aBorderStyle) {
+ if (aBorderStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
+ static Float dot[] = { 1.f, 1.f };
+ aStrokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dot);
+ aStrokeOptions.mDashPattern = dot;
+ } else if (aBorderStyle == NS_STYLE_BORDER_STYLE_DASHED) {
+ static Float dash[] = { 5.f, 5.f };
+ aStrokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ aStrokeOptions.mDashPattern = dash;
+ } else {
+ aStrokeOptions.mDashLength = 0;
+ aStrokeOptions.mDashPattern = nullptr;
+ }
+ }
+
+ /**
+ * Convert an nsRect to a gfxRect.
+ */
+ static gfxRect RectToGfxRect(const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel);
+
+ static gfxPoint PointToGfxPoint(const nsPoint& aPoint,
+ int32_t aAppUnitsPerPixel) {
+ return gfxPoint(gfxFloat(aPoint.x) / aAppUnitsPerPixel,
+ gfxFloat(aPoint.y) / aAppUnitsPerPixel);
+ }
+
+ /**
+ * Draw a whole image without scaling or tiling.
+ *
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aDest The top-left where the image should be drawn.
+ * @param aDirty If non-null, then pixels outside this area may
+ * be skipped.
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety
+ * @param aSourceArea If non-null, this area is extracted from
+ * the image and drawn at aDest. It's
+ * in appunits. For best results it should
+ * be aligned with image pixels.
+ */
+ static DrawResult DrawSingleUnscaledImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsPoint& aDest,
+ const nsRect* aDirty,
+ uint32_t aImageFlags,
+ const nsRect* aSourceArea = nullptr);
+
+ /**
+ * Draw a whole image without tiling.
+ *
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aDest The area that the image should fill.
+ * @param aDirty Pixels outside this area may be skipped.
+ * @param aSVGContext If non-null, SVG-related rendering context
+ * such as overridden attributes on the image
+ * document's root <svg> node. Ignored for
+ * raster images.
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_*
+ * variety.
+ * @param aAnchor If non-null, a point which we will ensure
+ * is pixel-aligned in the output.
+ * @param aSourceArea If non-null, this area is extracted from
+ * the image and drawn in aDest. It's
+ * in appunits. For best results it should
+ * be aligned with image pixels.
+ */
+ static DrawResult DrawSingleImage(gfxContext& aContext,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest,
+ const nsRect& aDirty,
+ const mozilla::SVGImageContext* aSVGContext,
+ uint32_t aImageFlags,
+ const nsPoint* aAnchorPoint = nullptr,
+ const nsRect* aSourceArea = nullptr);
+
+ /**
+ * Given an imgIContainer, this method attempts to obtain an intrinsic
+ * px-valued height & width for it. If the imgIContainer has a non-pixel
+ * value for either height or width, this method tries to generate a pixel
+ * value for that dimension using the intrinsic ratio (if available). The
+ * intrinsic ratio will be assigned to aIntrinsicRatio; if there's no
+ * intrinsic ratio then (0, 0) will be assigned.
+ *
+ * This method will always set aGotWidth and aGotHeight to indicate whether
+ * we were able to successfully obtain (or compute) a value for each
+ * dimension.
+ *
+ * NOTE: This method is similar to ComputeSizeWithIntrinsicDimensions. The
+ * difference is that this one is simpler and is suited to places where we
+ * have less information about the frame tree.
+ */
+ static void ComputeSizeForDrawing(imgIContainer* aImage,
+ CSSIntSize& aImageSize,
+ nsSize& aIntrinsicRatio,
+ bool& aGotWidth,
+ bool& aGotHeight);
+
+ /**
+ * Given an imgIContainer, this method attempts to obtain an intrinsic
+ * px-valued height & width for it. If the imgIContainer has a non-pixel
+ * value for either height or width, this method tries to generate a pixel
+ * value for that dimension using the intrinsic ratio (if available). If,
+ * after trying all these methods, no value is available for one or both
+ * dimensions, the corresponding dimension of aFallbackSize is used instead.
+ */
+ static CSSIntSize
+ ComputeSizeForDrawingWithFallback(imgIContainer* aImage,
+ const nsSize& aFallbackSize);
+
+ /**
+ * Given a source area of an image (in appunits) and a destination area
+ * that we want to map that source area too, computes the area that
+ * would be covered by the whole image. This is useful for passing to
+ * the aDest parameter of DrawImage, when we want to draw a subimage
+ * of an overall image.
+ */
+ static nsRect GetWholeImageDestination(const nsSize& aWholeImageSize,
+ const nsRect& aImageSourceArea,
+ const nsRect& aDestArea);
+
+ /**
+ * Given an image container and an orientation, returns an image container
+ * that contains the same image, reoriented appropriately. May return the
+ * original image container if no changes are needed.
+ *
+ * @param aContainer The image container to apply the orientation to.
+ * @param aOrientation The desired orientation.
+ */
+ static already_AddRefed<imgIContainer>
+ OrientImage(imgIContainer* aContainer,
+ const nsStyleImageOrientation& aOrientation);
+
+ /**
+ * Determine if any corner radius is of nonzero size
+ * @param aCorners the |nsStyleCorners| object to check
+ * @return true unless all the coordinates are 0%, 0 or null.
+ *
+ * A corner radius with one dimension zero and one nonzero is
+ * treated as a nonzero-radius corner, even though it will end up
+ * being rendered like a zero-radius corner. This is because such
+ * corners are not expected to appear outside of test cases, and it's
+ * simpler to implement the test this way.
+ */
+ static bool HasNonZeroCorner(const nsStyleCorners& aCorners);
+
+ /**
+ * Determine if there is any corner radius on corners adjacent to the
+ * given side.
+ */
+ static bool HasNonZeroCornerOnSide(const nsStyleCorners& aCorners,
+ mozilla::css::Side aSide);
+
+ /**
+ * Determine if a widget is likely to require transparency or translucency.
+ * @param aBackgroundFrame The frame that the background is set on. For
+ * <window>s, this will be the canvas frame.
+ * @param aCSSRootFrame The frame that holds CSS properties affecting
+ * the widget's transparency. For menupopups,
+ * aBackgroundFrame and aCSSRootFrame will be the
+ * same.
+ * @return a value suitable for passing to SetWindowTranslucency.
+ */
+ static nsTransparencyMode GetFrameTransparency(nsIFrame* aBackgroundFrame,
+ nsIFrame* aCSSRootFrame);
+
+ /**
+ * A frame is a popup if it has its own floating window. Menus, panels
+ * and combobox dropdowns are popups.
+ */
+ static bool IsPopup(nsIFrame* aFrame);
+
+ /**
+ * Find the nearest "display root". This is the nearest enclosing
+ * popup frame or the root prescontext's root frame.
+ */
+ static nsIFrame* GetDisplayRootFrame(nsIFrame* aFrame);
+
+ /**
+ * Get the reference frame that would be used when constructing a
+ * display item for this frame. Rather than using their own frame
+ * as a reference frame.)
+ *
+ * This duplicates some of the logic of GetDisplayRootFrame above and
+ * of nsDisplayListBuilder::FindReferenceFrameFor.
+ *
+ * If you have an nsDisplayListBuilder, you should get the reference
+ * frame from it instead of calling this.
+ */
+ static nsIFrame* GetReferenceFrame(nsIFrame* aFrame);
+
+ /**
+ * Get textrun construction flags determined by a given style; in particular
+ * some combination of:
+ * -- TEXT_DISABLE_OPTIONAL_LIGATURES if letter-spacing is in use
+ * -- TEXT_OPTIMIZE_SPEED if the text-rendering CSS property and font size
+ * and prefs indicate we should be optimizing for speed over quality
+ */
+ static uint32_t GetTextRunFlagsForStyle(nsStyleContext* aStyleContext,
+ const nsStyleFont* aStyleFont,
+ const nsStyleText* aStyleText,
+ nscoord aLetterSpacing);
+
+ /**
+ * Get orientation flags for textrun construction.
+ */
+ static uint32_t GetTextRunOrientFlagsForStyle(nsStyleContext* aStyleContext);
+
+ /**
+ * Takes two rectangles whose origins must be the same, and computes
+ * the difference between their union and their intersection as two
+ * rectangles. (This difference is a superset of the difference
+ * between the two rectangles.)
+ */
+ static void GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2,
+ nsRect* aHStrip, nsRect* aVStrip);
+
+ /**
+ * Get a device context that can be used to get up-to-date device
+ * dimensions for the given window. For some reason, this is more
+ * complicated than it ought to be in multi-monitor situations.
+ */
+ static nsDeviceContext*
+ GetDeviceContextForScreenInfo(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Some frames with 'position: fixed' (nsStylePosition::mDisplay ==
+ * NS_STYLE_POSITION_FIXED) are not really fixed positioned, since
+ * they're inside an element with -moz-transform. This function says
+ * whether such an element is a real fixed-pos element.
+ */
+ static bool IsReallyFixedPos(nsIFrame* aFrame);
+
+ /**
+ * Obtain a SourceSurface from the given DOM element, if possible.
+ * This obtains the most natural surface from the element; that
+ * is, the one that can be obtained with the fewest conversions.
+ *
+ * The flags below can modify the behaviour of this function. The
+ * result is returned as a SurfaceFromElementResult struct, also
+ * defined below.
+ *
+ * Currently, this will do:
+ * - HTML Canvas elements: will return the underlying canvas surface
+ * - HTML Video elements: will return the current video frame
+ * - Image elements: will return the image
+ *
+ * The above results are modified by the below flags (copying,
+ * forcing image surface, etc.).
+ */
+
+ enum {
+ /* When creating a new surface, create an image surface */
+ SFE_WANT_IMAGE_SURFACE = 1 << 0,
+ /* Whether to extract the first frame (as opposed to the
+ current frame) in the case that the element is an image. */
+ SFE_WANT_FIRST_FRAME = 1 << 1,
+ /* Whether we should skip colorspace/gamma conversion */
+ SFE_NO_COLORSPACE_CONVERSION = 1 << 2,
+ /* Specifies that the caller wants unpremultiplied pixel data.
+ If this is can be done efficiently, the result will be a
+ DataSourceSurface and mIsPremultiplied with be set to false. */
+ SFE_PREFER_NO_PREMULTIPLY_ALPHA = 1 << 3,
+ /* Whether we should skip getting a surface for vector images and
+ return a DirectDrawInfo containing an imgIContainer instead. */
+ SFE_NO_RASTERIZING_VECTORS = 1 << 4,
+ /* If image type is vector, the return surface size will same as
+ element size, not image's intrinsic size. */
+ SFE_USE_ELEMENT_SIZE_IF_VECTOR = 1 << 5
+ };
+
+ struct DirectDrawInfo {
+ /* imgIContainer to directly draw to a context */
+ nsCOMPtr<imgIContainer> mImgContainer;
+ /* which frame to draw */
+ uint32_t mWhichFrame;
+ /* imgIContainer flags to use when drawing */
+ uint32_t mDrawingFlags;
+ };
+
+ struct SurfaceFromElementResult {
+ friend class mozilla::dom::CanvasRenderingContext2D;
+ friend class nsLayoutUtils;
+
+ /* If SFEResult contains a valid surface, it either mLayersImage or mSourceSurface
+ * will be non-null, and GetSourceSurface() will not be null.
+ *
+ * For valid surfaces, mSourceSurface may be null if mLayersImage is non-null, but
+ * GetSourceSurface() will create mSourceSurface from mLayersImage when called.
+ */
+
+ /* Video elements (at least) often are already decoded as layers::Images. */
+ RefPtr<mozilla::layers::Image> mLayersImage;
+
+protected:
+ /* GetSourceSurface() fills this and returns its non-null value if this SFEResult
+ * was successful. */
+ RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
+
+public:
+ /* Contains info for drawing when there is no mSourceSurface. */
+ DirectDrawInfo mDrawInfo;
+
+ /* The size of the surface */
+ mozilla::gfx::IntSize mSize;
+ /* The principal associated with the element whose surface was returned.
+ If there is a surface, this will never be null. */
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ /* The image request, if the element is an nsIImageLoadingContent */
+ nsCOMPtr<imgIRequest> mImageRequest;
+ /* Whether the element was "write only", that is, the bits should not be exposed to content */
+ bool mIsWriteOnly;
+ /* Whether the element was still loading. Some consumers need to handle
+ this case specially. */
+ bool mIsStillLoading;
+ /* Whether the element has a valid size. */
+ bool mHasSize;
+ /* Whether the element used CORS when loading. */
+ bool mCORSUsed;
+ /* Whether the returned image contains premultiplied pixel data */
+ bool mIsPremultiplied;
+
+ // Methods:
+
+ SurfaceFromElementResult();
+
+ // Gets mSourceSurface, or makes a SourceSurface from mLayersImage.
+ const RefPtr<mozilla::gfx::SourceSurface>& GetSourceSurface();
+ };
+
+ // This function can be called on any thread.
+ static SurfaceFromElementResult
+ SurfaceFromOffscreenCanvas(mozilla::dom::OffscreenCanvas *aOffscreenCanvas,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static SurfaceFromElementResult
+ SurfaceFromOffscreenCanvas(mozilla::dom::OffscreenCanvas *aOffscreenCanvas,
+ uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromOffscreenCanvas(aOffscreenCanvas, aSurfaceFlags, target);
+ }
+
+ static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::Element *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::Element *aElement,
+ uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromElement(aElement, aSurfaceFlags, target);
+ }
+
+ static SurfaceFromElementResult SurfaceFromElement(nsIImageLoadingContent *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ // Need an HTMLImageElement overload, because otherwise the
+ // nsIImageLoadingContent and mozilla::dom::Element overloads are ambiguous
+ // for HTMLImageElement.
+ static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::HTMLImageElement *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::HTMLCanvasElement *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static SurfaceFromElementResult SurfaceFromElement(mozilla::dom::HTMLVideoElement *aElement,
+ uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+
+ /**
+ * When the document is editable by contenteditable attribute of its root
+ * content or body content.
+ *
+ * Be aware, this returns nullptr if it's in designMode.
+ *
+ * For example:
+ *
+ * <html contenteditable="true"><body></body></html>
+ * returns the <html>.
+ *
+ * <html><body contenteditable="true"></body></html>
+ * <body contenteditable="true"></body>
+ * With these cases, this returns the <body>.
+ * NOTE: The latter case isn't created normally, however, it can be
+ * created by script with XHTML.
+ *
+ * <body><p contenteditable="true"></p></body>
+ * returns nullptr because <body> isn't editable.
+ */
+ static nsIContent*
+ GetEditableRootContentByContentEditable(nsIDocument* aDocument);
+
+ /**
+ * Returns true if the passed in prescontext needs the dark grey background
+ * that goes behind the page of a print preview presentation.
+ */
+ static bool NeedsPrintPreviewBackground(nsPresContext* aPresContext);
+
+ /**
+ * Adds all font faces used in the frame tree starting from aFrame
+ * to the list aFontFaceList.
+ */
+ static nsresult GetFontFacesForFrames(nsIFrame* aFrame,
+ nsFontFaceList* aFontFaceList);
+
+ /**
+ * Adds all font faces used within the specified range of text in aFrame,
+ * and optionally its continuations, to the list in aFontFaceList.
+ * Pass 0 and INT32_MAX for aStartOffset and aEndOffset to specify the
+ * entire text is to be considered.
+ */
+ static nsresult GetFontFacesForText(nsIFrame* aFrame,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ bool aFollowContinuations,
+ nsFontFaceList* aFontFaceList);
+
+ /**
+ * Walks the frame tree starting at aFrame looking for textRuns.
+ * If |clear| is true, just clears the TEXT_RUN_MEMORY_ACCOUNTED flag
+ * on each textRun found (and |aMallocSizeOf| is not used).
+ * If |clear| is false, adds the storage used for each textRun to the
+ * total, and sets the TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double-
+ * accounting. (Runs with this flag already set will be skipped.)
+ * Expected usage pattern is therefore to call twice:
+ * (void)SizeOfTextRunsForFrames(rootFrame, nullptr, true);
+ * total = SizeOfTextRunsForFrames(rootFrame, mallocSizeOf, false);
+ */
+ static size_t SizeOfTextRunsForFrames(nsIFrame* aFrame,
+ mozilla::MallocSizeOf aMallocSizeOf,
+ bool clear);
+
+ /**
+ * Returns true if the frame has any current CSS transitions.
+ * A current transition is any transition that has not yet finished playing
+ * including paused transitions.
+ */
+ static bool HasCurrentTransitions(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if |aFrame| has an animation of |aProperty| regardless of
+ * whether the property is overridden by !important rule.
+ */
+ static bool HasAnimationOfProperty(const nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+
+ /**
+ * Returns true if |aFrame| has an animation of |aProperty| which is
+ * not overridden by !important rules.
+ */
+ static bool HasEffectiveAnimation(const nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+
+ /**
+ * Checks if off-main-thread animations are enabled.
+ */
+ static bool AreAsyncAnimationsEnabled();
+
+ /**
+ * Checks if we should warn about animations that can't be async
+ */
+ static bool IsAnimationLoggingEnabled();
+
+ /**
+ * Find a suitable scale for a element (aFrame's content) over the course of any
+ * animations and transitions of the CSS transform property on the
+ * element that run on the compositor thread.
+ * It will check the maximum and minimum scale during the animations and
+ * transitions and return a suitable value for performance and quality.
+ * Will return scale(1,1) if there are no such animations.
+ * Always returns a positive value.
+ * @param aVisibleSize is the size of the area we want to paint
+ * @param aDisplaySize is the size of the display area of the pres context
+ */
+ static gfxSize ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
+ const nsSize& aVisibleSize,
+ const nsSize& aDisplaySize);
+
+ /**
+ * Checks whether we want to use the GPU to scale images when
+ * possible.
+ */
+ static bool GPUImageScalingEnabled();
+
+ /**
+ * Checks whether we want to layerize animated images whenever possible.
+ */
+ static bool AnimatedImageLayersEnabled();
+
+ /**
+ * Checks if we should enable parsing for CSS Filters.
+ */
+ static bool CSSFiltersEnabled();
+
+ /**
+ * Checks if we should enable parsing for CSS clip-path basic shapes.
+ */
+ static bool CSSClipPathShapesEnabled();
+
+ /**
+ * Checks whether support for the CSS-wide "unset" value is enabled.
+ */
+ static bool UnsetValueEnabled();
+
+ /**
+ * Checks whether support for the CSS grid-template-{columns,rows} 'subgrid X'
+ * value is enabled.
+ */
+ static bool IsGridTemplateSubgridValueEnabled();
+
+ /**
+ * Checks whether support for the CSS text-align (and text-align-last)
+ * 'true' value is enabled.
+ */
+ static bool IsTextAlignUnsafeValueEnabled();
+
+ /**
+ * Checks if CSS variables are currently enabled.
+ */
+ static bool CSSVariablesEnabled()
+ {
+ return sCSSVariablesEnabled;
+ }
+
+ static bool InterruptibleReflowEnabled()
+ {
+ return sInterruptibleReflowEnabled;
+ }
+
+ /**
+ * Unions the overflow areas of the children of aFrame with aOverflowAreas.
+ * aSkipChildLists specifies any child lists that should be skipped.
+ * kSelectPopupList and kPopupList are always skipped.
+ */
+ static void UnionChildOverflow(nsIFrame* aFrame,
+ nsOverflowAreas& aOverflowAreas,
+ mozilla::layout::FrameChildListIDs aSkipChildLists =
+ mozilla::layout::FrameChildListIDs());
+
+ /**
+ * Return the font size inflation *ratio* for a given frame. This is
+ * the factor by which font sizes should be inflated; it is never
+ * smaller than 1.
+ */
+ static float FontSizeInflationFor(const nsIFrame *aFrame);
+
+ /**
+ * Perform the first half of the computation of FontSizeInflationFor
+ * (see above).
+ * This includes determining whether inflation should be performed
+ * within this container and returning 0 if it should not be.
+ *
+ * The result is guaranteed not to vary between line participants
+ * (inlines, text frames) within a line.
+ *
+ * The result should not be used directly since font sizes slightly
+ * above the minimum should always be adjusted as done by
+ * FontSizeInflationInner.
+ */
+ static nscoord InflationMinFontSizeFor(const nsIFrame *aFrame);
+
+ /**
+ * Perform the second half of the computation done by
+ * FontSizeInflationFor (see above).
+ *
+ * aMinFontSize must be the result of one of the
+ * InflationMinFontSizeFor methods above.
+ */
+ static float FontSizeInflationInner(const nsIFrame *aFrame,
+ nscoord aMinFontSize);
+
+ static bool FontSizeInflationEnabled(nsPresContext *aPresContext);
+
+ /**
+ * See comment above "font.size.inflation.maxRatio" in
+ * modules/libpref/src/init/all.js .
+ */
+ static uint32_t FontSizeInflationMaxRatio() {
+ return sFontSizeInflationMaxRatio;
+ }
+
+ /**
+ * See comment above "font.size.inflation.emPerLine" in
+ * modules/libpref/src/init/all.js .
+ */
+ static uint32_t FontSizeInflationEmPerLine() {
+ return sFontSizeInflationEmPerLine;
+ }
+
+ /**
+ * See comment above "font.size.inflation.minTwips" in
+ * modules/libpref/src/init/all.js .
+ */
+ static uint32_t FontSizeInflationMinTwips() {
+ return sFontSizeInflationMinTwips;
+ }
+
+ /**
+ * See comment above "font.size.inflation.lineThreshold" in
+ * modules/libpref/src/init/all.js .
+ */
+ static uint32_t FontSizeInflationLineThreshold() {
+ return sFontSizeInflationLineThreshold;
+ }
+
+ static bool FontSizeInflationForceEnabled() {
+ return sFontSizeInflationForceEnabled;
+ }
+
+ static bool FontSizeInflationDisabledInMasterProcess() {
+ return sFontSizeInflationDisabledInMasterProcess;
+ }
+
+ static bool SVGTransformBoxEnabled() {
+ return sSVGTransformBoxEnabled;
+ }
+
+ static bool TextCombineUprightDigitsEnabled() {
+ return sTextCombineUprightDigitsEnabled;
+ }
+
+ // Stylo (the Servo backend for Gecko's style system) is generally enabled
+ // or disabled at compile-time. However, we provide the additional capability
+ // to disable it dynamically in stylo-enabled builds via a pref.
+ static bool StyloEnabled() {
+#ifdef MOZ_STYLO
+ return sStyloEnabled;
+#else
+ return false;
+#endif
+ }
+
+ static uint32_t IdlePeriodDeadlineLimit() {
+ return sIdlePeriodDeadlineLimit;
+ }
+
+ static uint32_t QuiescentFramesBeforeIdlePeriod() {
+ return sQuiescentFramesBeforeIdlePeriod;
+ }
+
+ /**
+ * See comment above "font.size.inflation.mappingIntercept" in
+ * modules/libpref/src/init/all.js .
+ */
+ static int32_t FontSizeInflationMappingIntercept() {
+ return sFontSizeInflationMappingIntercept;
+ }
+
+ /**
+ * Returns true if the nglayout.debug.invalidation pref is set to true.
+ * Note that sInvalidationDebuggingIsEnabled is declared outside this function to
+ * allow it to be accessed an manipulated from breakpoint conditions.
+ */
+ static bool InvalidationDebuggingIsEnabled() {
+ return sInvalidationDebuggingIsEnabled || getenv("MOZ_DUMP_INVALIDATION") != 0;
+ }
+
+ static void Initialize();
+ static void Shutdown();
+
+ /**
+ * Register an imgIRequest object with a refresh driver.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * register with.
+ * @param aRequest A pointer to the imgIRequest object which the client wants
+ * to register with the refresh driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is true, then this request will not be
+ * registered again. If the request is registered by this function,
+ * then *aRequestRegistered will be set to true upon the completion of
+ * this function.
+ *
+ */
+ static void RegisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Register an imgIRequest object with a refresh driver, but only if the
+ * request is for an image that is animated.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * register with.
+ * @param aRequest A pointer to the imgIRequest object which the client wants
+ * to register with the refresh driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is true, then this request will not be
+ * registered again. If the request is registered by this function,
+ * then *aRequestRegistered will be set to true upon the completion of
+ * this function.
+ *
+ */
+ static void RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Deregister an imgIRequest object from a refresh driver.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * deregister from.
+ * @param aRequest A pointer to the imgIRequest object with which the client
+ * previously registered and now wants to deregister from the refresh
+ * driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is false, then this request will not be
+ * deregistered. If the request is deregistered by this function,
+ * then *aRequestRegistered will be set to false upon the completion of
+ * this function.
+ */
+ static void DeregisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Shim to nsCSSFrameConstructor::PostRestyleEvent. Exists so that we
+ * can avoid including nsCSSFrameConstructor.h and all its dependencies
+ * in content files.
+ */
+ static void PostRestyleEvent(mozilla::dom::Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint);
+
+ /**
+ * Updates a pair of x and y distances if a given point is closer to a given
+ * rectangle than the original distance values. If aPoint is closer to
+ * aRect than aClosestXDistance and aClosestYDistance indicate, then those
+ * two variables are updated with the distance between aPoint and aRect,
+ * and true is returned. If aPoint is not closer, then aClosestXDistance
+ * and aClosestYDistance are left unchanged, and false is returned.
+ *
+ * Distances are measured in the two dimensions separately; a closer x
+ * distance beats a closer y distance.
+ */
+ template<typename PointType, typename RectType, typename CoordType>
+ static bool PointIsCloserToRect(PointType aPoint, const RectType& aRect,
+ CoordType& aClosestXDistance,
+ CoordType& aClosestYDistance);
+ /**
+ * Computes the box shadow rect for the frame, or returns an empty rect if
+ * there are no shadows.
+ *
+ * @param aFrame Frame to compute shadows for.
+ * @param aFrameSize Size of aFrame (in case it hasn't been set yet).
+ */
+ static nsRect GetBoxShadowRectForFrame(nsIFrame* aFrame, const nsSize& aFrameSize);
+
+#ifdef DEBUG
+ /**
+ * Assert that there are no duplicate continuations of the same frame
+ * within aFrameList. Optimize the tests by assuming that all frames
+ * in aFrameList have parent aContainer.
+ */
+ static void
+ AssertNoDuplicateContinuations(nsIFrame* aContainer,
+ const nsFrameList& aFrameList);
+
+ /**
+ * Assert that the frame tree rooted at |aSubtreeRoot| is empty, i.e.,
+ * that it contains no first-in-flows.
+ */
+ static void
+ AssertTreeOnlyEmptyNextInFlows(nsIFrame *aSubtreeRoot);
+#endif
+
+ /**
+ * Helper method to get touch action behaviour from the frame
+ */
+ static uint32_t
+ GetTouchActionFromFrame(nsIFrame* aFrame);
+
+ /**
+ * Helper method to transform |aBounds| from aFrame to aAncestorFrame,
+ * and combine it with |aPreciseTargetDest| if it is axis-aligned, or
+ * combine it with |aImpreciseTargetDest| if not.
+ */
+ static void
+ TransformToAncestorAndCombineRegions(
+ const nsRegion& aRegion,
+ nsIFrame* aFrame,
+ const nsIFrame* aAncestorFrame,
+ nsRegion* aPreciseTargetDest,
+ nsRegion* aImpreciseTargetDest,
+ mozilla::Maybe<Matrix4x4>* aMatrixCache);
+
+ /**
+ * Populate aOutSize with the size of the content viewer corresponding
+ * to the given prescontext. Return true if the size was set, false
+ * otherwise.
+ */
+ static bool
+ GetContentViewerSize(nsPresContext* aPresContext,
+ LayoutDeviceIntSize& aOutSize);
+
+ /**
+ * Calculate the compostion size for a frame. See FrameMetrics.h for
+ * defintion of composition size (or bounds).
+ * Note that for the root content document's root scroll frame (RCD-RSF),
+ * the returned size does not change as the document's resolution changes,
+ * but for all other frames it does. This means that callers that pass in
+ * a frame that may or may not be the RCD-RSF (which is most callers),
+ * are likely to need special-case handling of the RCD-RSF.
+ */
+ static nsSize
+ CalculateCompositionSizeForFrame(nsIFrame* aFrame, bool aSubtractScrollbars = true);
+
+ /**
+ * Calculate the composition size for the root scroll frame of the root
+ * content document.
+ * @param aFrame A frame in the root content document (or a descendant of it).
+ * @param aIsRootContentDocRootScrollFrame Whether aFrame is already the root
+ * scroll frame of the root content document. In this case we just
+ * use aFrame's own composition size.
+ * @param aMetrics A partially populated FrameMetrics for aFrame. Must have at
+ * least mCompositionBounds, mCumulativeResolution, and
+ * mDevPixelsPerCSSPixel set.
+ */
+ static CSSSize
+ CalculateRootCompositionSize(nsIFrame* aFrame,
+ bool aIsRootContentDocRootScrollFrame,
+ const FrameMetrics& aMetrics);
+
+ /**
+ * Calculate the scrollable rect for a frame. See FrameMetrics.h for
+ * defintion of scrollable rect. aScrollableFrame is the scroll frame to calculate
+ * the scrollable rect for. If it's null then we calculate the scrollable rect
+ * as the rect of the root frame.
+ */
+ static nsRect
+ CalculateScrollableRectForFrame(nsIScrollableFrame* aScrollableFrame, nsIFrame* aRootFrame);
+
+ /**
+ * Calculate the expanded scrollable rect for a frame. See FrameMetrics.h for
+ * defintion of expanded scrollable rect.
+ */
+ static nsRect
+ CalculateExpandedScrollableRect(nsIFrame* aFrame);
+
+ /**
+ * Returns true if the widget owning the given frame uses asynchronous
+ * scrolling.
+ */
+ static bool UsesAsyncScrolling(nsIFrame* aFrame);
+
+ /**
+ * Returns true if the widget owning the given frame has builtin APZ support
+ * enabled.
+ */
+ static bool AsyncPanZoomEnabled(nsIFrame* aFrame);
+
+ /**
+ * Returns the current APZ Resolution Scale. When Java Pan/Zoom is
+ * enabled in Fennec it will always return 1.0.
+ */
+ static float GetCurrentAPZResolutionScale(nsIPresShell* aShell);
+
+ /**
+ * Log a key/value pair for APZ testing during a paint.
+ * @param aManager The data will be written to the APZTestData associated
+ * with this layer manager.
+ * @param aScrollId Identifies the scroll frame to which the data pertains.
+ * @param aKey The key under which to log the data.
+ * @param aValue The value of the data to be logged.
+ */
+ static void LogTestDataForPaint(mozilla::layers::LayerManager* aManager,
+ ViewID aScrollId,
+ const std::string& aKey,
+ const std::string& aValue) {
+ if (IsAPZTestLoggingEnabled()) {
+ DoLogTestDataForPaint(aManager, aScrollId, aKey, aValue);
+ }
+ }
+
+ /**
+ * A convenience overload of LogTestDataForPaint() that accepts any type
+ * as the value, and passes it through mozilla::ToString() to obtain a string
+ * value. The type passed must support streaming to an std::ostream.
+ */
+ template <typename Value>
+ static void LogTestDataForPaint(mozilla::layers::LayerManager* aManager,
+ ViewID aScrollId,
+ const std::string& aKey,
+ const Value& aValue) {
+ if (IsAPZTestLoggingEnabled()) {
+ DoLogTestDataForPaint(aManager, aScrollId, aKey,
+ mozilla::ToString(aValue));
+ }
+ }
+
+ /**
+ * Calculate a basic FrameMetrics with enough fields set to perform some
+ * layout calculations. The fields set are dev-to-css ratio, pres shell
+ * resolution, cumulative resolution, zoom, composition size, root
+ * composition size, scroll offset and scrollable rect.
+ *
+ * By contrast, ComputeFrameMetrics() computes all the fields, but requires
+ * extra inputs and can only be called during frame layer building.
+ */
+ static FrameMetrics CalculateBasicFrameMetrics(nsIScrollableFrame* aScrollFrame);
+
+ /**
+ * 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.
+ *
+ * This is intended to be called during display list building.
+ */
+ static void MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder,
+ nsIFrame* aScrollFrame);
+
+ static nsIScrollableFrame* GetAsyncScrollableAncestorFrame(nsIFrame* aTarget);
+
+ /**
+ * Sets a zero margin display port on all proper ancestors of aFrame that
+ * are async scrollable.
+ */
+ static void SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame,
+ RepaintMode aRepaintMode);
+ /**
+ * 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);
+
+ static bool IsOutlineStyleAutoEnabled();
+
+ static void SetBSizeFromFontMetrics(const nsIFrame* aFrame,
+ mozilla::ReflowOutput& aMetrics,
+ const mozilla::LogicalMargin& aFramePadding,
+ mozilla::WritingMode aLineWM,
+ mozilla::WritingMode aFrameWM);
+
+ static bool HasDocumentLevelListenersForApzAwareEvents(nsIPresShell* aShell);
+
+ /**
+ * Set the scroll port size for the purpose of clamping the scroll position
+ * for the root scroll frame of this document
+ * (see nsIDOMWindowUtils.setScrollPositionClampingScrollPortSize).
+ */
+ static void SetScrollPositionClampingScrollPortSize(nsIPresShell* aPresShell,
+ CSSSize aSize);
+
+ /**
+ * Returns true if the given scroll origin is "higher priority" than APZ.
+ * In general any content programmatic scrolls (e.g. scrollTo calls) are
+ * higher priority, and take precedence over APZ scrolling. This function
+ * returns true for those, and returns false for other origins like APZ
+ * itself, or scroll position updates from the history restore code.
+ */
+ static bool CanScrollOriginClobberApz(nsIAtom* aScrollOrigin);
+
+ static ScrollMetadata ComputeScrollMetadata(nsIFrame* aForFrame,
+ nsIFrame* aScrollFrame,
+ nsIContent* aContent,
+ const nsIFrame* aReferenceFrame,
+ Layer* aLayer,
+ ViewID aScrollParentId,
+ const nsRect& aViewport,
+ const mozilla::Maybe<nsRect>& aClipRect,
+ bool aIsRoot,
+ const ContainerLayerParameters& aContainerParameters);
+
+ /**
+ * If the given scroll frame needs an area excluded from its composition
+ * bounds due to scrollbars, return that area, otherwise return an empty
+ * margin.
+ * There is no need to exclude scrollbars in the following cases:
+ * - If the scroll frame is not the RCD-RSF; in that case, the composition
+ * bounds is calculated based on the scroll port which already excludes
+ * the scrollbar area.
+ * - If the scrollbars are overlay, since then they are drawn on top of the
+ * scrollable content.
+ */
+ static nsMargin ScrollbarAreaToExcludeFromCompositionBoundsFor(nsIFrame* aScrollFrame);
+
+ /**
+ * Looks in the layer subtree rooted at aLayer for a metrics with scroll id
+ * aScrollId. Returns true if such is found.
+ */
+ static bool ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId);
+
+ static bool ShouldUseNoScriptSheet(nsIDocument* aDocument);
+ static bool ShouldUseNoFramesSheet(nsIDocument* aDocument);
+
+ /**
+ * Get the text content inside the frame. This methods traverse the
+ * frame tree and collect the content from text frames. Note that this
+ * method is similiar to nsContentUtils::GetNodeTextContent, but it at
+ * least differs from that method in the following things:
+ * 1. it skips text content inside nodes like style, script, textarea
+ * which don't generate an in-tree text frame for the text;
+ * 2. it skips elements with display property set to none;
+ * 3. it skips out-of-flow elements;
+ * 4. it includes content inside pseudo elements;
+ * 5. it may include part of text content of a node if a text frame
+ * inside is split to different continuations.
+ */
+ static void GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
+ /**
+ * Same as GetFrameTextContent but appends the result rather than sets it.
+ */
+ static void AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
+ /**
+ * Takes a selection, and returns selection's bounding rect which is relative
+ * to its root frame.
+ *
+ * @param aSel Selection to check
+ */
+ static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel);
+
+ /**
+ * Calculate the bounding rect of |aContent|, relative to the origin
+ * of the scrolled content of |aRootScrollFrame|.
+ * Where the element is contained inside a scrollable subframe, the
+ * bounding rect is clipped to the bounds of the subframe.
+ */
+ static CSSRect GetBoundingContentRect(const nsIContent* aContent,
+ const nsIScrollableFrame* aRootScrollFrame);
+
+ /**
+ * Returns the first ancestor who is a float containing block.
+ */
+ static nsBlockFrame* GetFloatContainingBlock(nsIFrame* aFrame);
+
+ /**
+ * Walks up the frame tree from |aForFrame| up to |aTopFrame|, or to the
+ * root of the frame tree if |aTopFrame| is nullptr, and returns true if
+ * a transformed frame is encountered.
+ */
+ static bool IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame = nullptr);
+
+ /**
+ * Walk up from aFrame to the cross-doc root, accumulating all the APZ callback
+ * transforms on the content elements encountered along the way. Return the
+ * accumulated value.
+ * XXX: Note that this does not take into account CSS transforms, nor
+ * differences in structure between the frame tree and the layer tree (which
+ * is probably what we *want* to be computing).
+ */
+ static CSSPoint GetCumulativeApzCallbackTransform(nsIFrame* aFrame);
+
+ /*
+ * Returns whether the given document supports being rendered with a
+ * Servo-backed style system. This checks whether Stylo is enabled
+ * globally, that the document is an HTML document, and that it is
+ * being presented in a content docshell.
+ */
+ static bool SupportsServoStyleBackend(nsIDocument* aDocument);
+
+ /*
+ * Checks whether a node is an invisible break.
+ * If not, returns the first frame on the next line if such a next line exists.
+ *
+ * @return true if the node is an invisible break.
+ * aNextLineFrame is returned null in this case.
+ * false if the node causes a visible break or if the node is no break.
+ *
+ * @param aNextLineFrame assigned to first frame on the next line if such a
+ * next line exists, null otherwise.
+ */
+ static bool IsInvisibleBreak(nsINode* aNode, nsIFrame** aNextLineFrame = nullptr);
+
+private:
+ static uint32_t sFontSizeInflationEmPerLine;
+ static uint32_t sFontSizeInflationMinTwips;
+ static uint32_t sFontSizeInflationLineThreshold;
+ static int32_t sFontSizeInflationMappingIntercept;
+ static uint32_t sFontSizeInflationMaxRatio;
+ static bool sFontSizeInflationForceEnabled;
+ static bool sFontSizeInflationDisabledInMasterProcess;
+ static bool sInvalidationDebuggingIsEnabled;
+ static bool sCSSVariablesEnabled;
+ static bool sInterruptibleReflowEnabled;
+ static bool sSVGTransformBoxEnabled;
+ static bool sTextCombineUprightDigitsEnabled;
+#ifdef MOZ_STYLO
+ static bool sStyloEnabled;
+#endif
+ static uint32_t sIdlePeriodDeadlineLimit;
+ static uint32_t sQuiescentFramesBeforeIdlePeriod;
+
+ /**
+ * Helper function for LogTestDataForPaint().
+ */
+ static void DoLogTestDataForPaint(mozilla::layers::LayerManager* aManager,
+ ViewID aScrollId,
+ const std::string& aKey,
+ const std::string& aValue);
+
+ static bool IsAPZTestLoggingEnabled();
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsLayoutUtils::PaintFrameFlags)
+
+template<typename PointType, typename RectType, typename CoordType>
+/* static */ bool
+nsLayoutUtils::PointIsCloserToRect(PointType aPoint, const RectType& aRect,
+ CoordType& aClosestXDistance,
+ CoordType& aClosestYDistance)
+{
+ CoordType fromLeft = aPoint.x - aRect.x;
+ CoordType fromRight = aPoint.x - aRect.XMost();
+
+ CoordType xDistance;
+ if (fromLeft >= 0 && fromRight <= 0) {
+ xDistance = 0;
+ } else {
+ xDistance = std::min(abs(fromLeft), abs(fromRight));
+ }
+
+ if (xDistance <= aClosestXDistance) {
+ if (xDistance < aClosestXDistance) {
+ aClosestYDistance = std::numeric_limits<CoordType>::max();
+ }
+
+ CoordType fromTop = aPoint.y - aRect.y;
+ CoordType fromBottom = aPoint.y - aRect.YMost();
+
+ CoordType yDistance;
+ if (fromTop >= 0 && fromBottom <= 0) {
+ yDistance = 0;
+ } else {
+ yDistance = std::min(abs(fromTop), abs(fromBottom));
+ }
+
+ if (yDistance < aClosestYDistance) {
+ aClosestXDistance = xDistance;
+ aClosestYDistance = yDistance;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace mozilla {
+
+/**
+ * Converts an nsPoint in app units to a Moz2D Point in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ */
+inline gfx::Point NSPointToPoint(const nsPoint& aPoint,
+ int32_t aAppUnitsPerPixel) {
+ return gfx::Point(gfx::Float(aPoint.x) / aAppUnitsPerPixel,
+ gfx::Float(aPoint.y) / aAppUnitsPerPixel);
+}
+
+/**
+ * Converts an nsRect in app units to a Moz2D Rect in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ */
+gfx::Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel);
+
+/**
+ * Converts an nsRect in app units to a Moz2D Rect in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ *
+ * The passed DrawTarget is used to additionally snap the returned Rect to
+ * device pixels, if appropriate (as decided and carried out by Moz2D's
+ * MaybeSnapToDevicePixels helper, which this function calls to do any
+ * snapping).
+ */
+gfx::Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT);
+
+/**
+* Converts, where possible, an nsRect in app units to a Moz2D Rect in pixels
+* (whether those are device pixels or CSS px depends on what the caller
+* chooses to pass as aAppUnitsPerPixel).
+*
+* If snapping results in a rectangle with zero width or height, the affected
+* coordinates are left unsnapped
+*/
+gfx::Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT);
+
+void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2,
+ int32_t aAppUnitsPerDevPixel,
+ gfx::DrawTarget& aDrawTarget,
+ const gfx::Pattern& aPattern,
+ const gfx::StrokeOptions& aStrokeOptions = gfx::StrokeOptions(),
+ const gfx::DrawOptions& aDrawOptions = gfx::DrawOptions());
+
+ namespace layout {
+
+ /**
+ * An RAII class which will, for the duration of its lifetime,
+ * **if** the frame given is a container for font size inflation,
+ * set the current inflation container on the pres context to null
+ * (and then, in its destructor, restore the old value).
+ */
+ class AutoMaybeDisableFontInflation {
+ public:
+ explicit AutoMaybeDisableFontInflation(nsIFrame *aFrame);
+
+ ~AutoMaybeDisableFontInflation();
+ private:
+ nsPresContext *mPresContext;
+ bool mOldValue;
+ };
+
+ void MaybeSetupTransactionIdAllocator(layers::LayerManager* aManager, nsView* aView);
+
+ } // namespace layout
+} // namespace mozilla
+
+class nsSetAttrRunnable : public mozilla::Runnable
+{
+public:
+ nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
+ const nsAString& aValue);
+ nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
+ int32_t aValue);
+
+ NS_DECL_NSIRUNNABLE
+
+ nsCOMPtr<nsIContent> mContent;
+ nsCOMPtr<nsIAtom> mAttrName;
+ nsAutoString mValue;
+};
+
+class nsUnsetAttrRunnable : public mozilla::Runnable
+{
+public:
+ nsUnsetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName);
+
+ NS_DECL_NSIRUNNABLE
+
+ nsCOMPtr<nsIContent> mContent;
+ nsCOMPtr<nsIAtom> mAttrName;
+};
+
+#endif // nsLayoutUtils_h__
diff --git a/layout/base/nsPresArena.cpp b/layout/base/nsPresArena.cpp
new file mode 100644
index 000000000..2b1f072df
--- /dev/null
+++ b/layout/base/nsPresArena.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/* arena allocation for the frame tree and closely-related objects */
+
+// Even on 32-bit systems, we allocate objects from the frame arena
+// that require 8-byte alignment. The cast to uintptr_t is needed
+// because plarena isn't as careful about mask construction as it
+// ought to be.
+#define ALIGN_SHIFT 3
+#define PL_ARENA_CONST_ALIGN_MASK ((uintptr_t(1) << ALIGN_SHIFT) - 1)
+#include "plarena.h"
+// plarena.h needs to be included first to make it use the above
+// PL_ARENA_CONST_ALIGN_MASK in this file.
+
+#include "nsPresArena.h"
+
+#include "mozilla/Poison.h"
+#include "nsDebug.h"
+#include "nsArenaMemoryStats.h"
+#include "nsPrintfCString.h"
+#include "nsStyleContext.h"
+
+#include <inttypes.h>
+
+using namespace mozilla;
+
+// Size to use for PLArena block allocations.
+static const size_t ARENA_PAGE_SIZE = 8192;
+
+nsPresArena::nsPresArena()
+{
+ PL_INIT_ARENA_POOL(&mPool, "PresArena", ARENA_PAGE_SIZE);
+}
+
+nsPresArena::~nsPresArena()
+{
+ ClearArenaRefPtrs();
+
+#if defined(MOZ_HAVE_MEM_CHECKS)
+ for (auto iter = mFreeLists.Iter(); !iter.Done(); iter.Next()) {
+ FreeList* entry = iter.Get();
+ nsTArray<void*>::index_type len;
+ while ((len = entry->mEntries.Length())) {
+ void* result = entry->mEntries.ElementAt(len - 1);
+ entry->mEntries.RemoveElementAt(len - 1);
+ MOZ_MAKE_MEM_UNDEFINED(result, entry->mEntrySize);
+ }
+ }
+#endif
+
+ PL_FinishArenaPool(&mPool);
+}
+
+/* inline */ void
+nsPresArena::ClearArenaRefPtrWithoutDeregistering(void* aPtr,
+ ArenaObjectID aObjectID)
+{
+ switch (aObjectID) {
+#define PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(name_) \
+ case eArenaObjectID_##name_: \
+ static_cast<ArenaRefPtr<name_>*>(aPtr)->ClearWithoutDeregistering(); \
+ return;
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+ default:
+ break;
+ }
+ switch (aObjectID) {
+#define PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(name_) \
+ case eArenaObjectID_##name_: \
+ MOZ_ASSERT(false, #name_ " must be declared in nsPresArenaObjectList.h "\
+ "with PRES_ARENA_OBJECT_SUPPORTS_ARENAREFPTR"); \
+ break;
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+ default:
+ MOZ_ASSERT(false, "unexpected ArenaObjectID value");
+ break;
+ }
+}
+
+void
+nsPresArena::ClearArenaRefPtrs()
+{
+ for (auto iter = mArenaRefPtrs.Iter(); !iter.Done(); iter.Next()) {
+ void* ptr = iter.Key();
+ ArenaObjectID id = iter.UserData();
+ ClearArenaRefPtrWithoutDeregistering(ptr, id);
+ }
+ mArenaRefPtrs.Clear();
+}
+
+void
+nsPresArena::ClearArenaRefPtrs(ArenaObjectID aObjectID)
+{
+ for (auto iter = mArenaRefPtrs.Iter(); !iter.Done(); iter.Next()) {
+ void* ptr = iter.Key();
+ ArenaObjectID id = iter.UserData();
+ if (id == aObjectID) {
+ ClearArenaRefPtrWithoutDeregistering(ptr, id);
+ iter.Remove();
+ }
+ }
+}
+
+void*
+nsPresArena::Allocate(uint32_t aCode, size_t aSize)
+{
+ MOZ_ASSERT(aSize > 0, "PresArena cannot allocate zero bytes");
+
+ // We only hand out aligned sizes
+ aSize = PL_ARENA_ALIGN(&mPool, aSize);
+
+ // If there is no free-list entry for this type already, we have
+ // to create one now, to record its size.
+ FreeList* list = mFreeLists.PutEntry(aCode);
+
+ nsTArray<void*>::index_type len = list->mEntries.Length();
+ if (list->mEntrySize == 0) {
+ MOZ_ASSERT(len == 0, "list with entries but no recorded size");
+ list->mEntrySize = aSize;
+ } else {
+ MOZ_ASSERT(list->mEntrySize == aSize,
+ "different sizes for same object type code");
+ }
+
+ void* result;
+ if (len > 0) {
+ // LIFO behavior for best cache utilization
+ result = list->mEntries.ElementAt(len - 1);
+ list->mEntries.RemoveElementAt(len - 1);
+#if defined(DEBUG)
+ {
+ MOZ_MAKE_MEM_DEFINED(result, list->mEntrySize);
+ char* p = reinterpret_cast<char*>(result);
+ char* limit = p + list->mEntrySize;
+ for (; p < limit; p += sizeof(uintptr_t)) {
+ uintptr_t val = *reinterpret_cast<uintptr_t*>(p);
+ if (val != mozPoisonValue()) {
+ MOZ_ReportAssertionFailure(
+ nsPrintfCString("PresArena: poison overwritten; "
+ "wanted %.16" PRIx64 " "
+ "found %.16" PRIx64 " "
+ "errors in bits %.16" PRIx64 " ",
+ uint64_t(mozPoisonValue()),
+ uint64_t(val),
+ uint64_t(mozPoisonValue() ^ val)).get(),
+ __FILE__, __LINE__);
+ MOZ_CRASH();
+ }
+ }
+ }
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(result, list->mEntrySize);
+ return result;
+ }
+
+ // Allocate a new chunk from the arena
+ list->mEntriesEverAllocated++;
+ PL_ARENA_ALLOCATE(result, &mPool, aSize);
+ if (!result) {
+ NS_ABORT_OOM(aSize);
+ }
+ return result;
+}
+
+void
+nsPresArena::Free(uint32_t aCode, void* aPtr)
+{
+ // Try to recycle this entry.
+ FreeList* list = mFreeLists.GetEntry(aCode);
+ MOZ_ASSERT(list, "no free list for pres arena object");
+ MOZ_ASSERT(list->mEntrySize > 0, "PresArena cannot free zero bytes");
+
+ mozWritePoison(aPtr, list->mEntrySize);
+
+ MOZ_MAKE_MEM_NOACCESS(aPtr, list->mEntrySize);
+ list->mEntries.AppendElement(aPtr);
+}
+
+void
+nsPresArena::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ nsArenaMemoryStats* aArenaStats)
+{
+ // We do a complicated dance here because we want to measure the
+ // space taken up by the different kinds of objects in the arena,
+ // but we don't have pointers to those objects. And even if we did,
+ // we wouldn't be able to use aMallocSizeOf on them, since they were
+ // allocated out of malloc'd chunks of memory. So we compute the
+ // size of the arena as known by malloc and we add up the sizes of
+ // all the objects that we care about. Subtracting these two
+ // quantities gives us a catch-all "other" number, which includes
+ // slop in the arena itself as well as the size of objects that
+ // we've not measured explicitly.
+
+ size_t mallocSize = PL_SizeOfArenaPoolExcludingPool(&mPool, aMallocSizeOf);
+ mallocSize += mFreeLists.SizeOfExcludingThis(aMallocSizeOf);
+
+ size_t totalSizeInFreeLists = 0;
+ for (auto iter = mFreeLists.Iter(); !iter.Done(); iter.Next()) {
+ FreeList* entry = iter.Get();
+
+ // Note that we're not measuring the size of the entries on the free
+ // list here. The free list knows how many objects we've allocated
+ // ever (which includes any objects that may be on the FreeList's
+ // |mEntries| at this point) and we're using that to determine the
+ // total size of objects allocated with a given ID.
+ size_t totalSize = entry->mEntrySize * entry->mEntriesEverAllocated;
+ size_t* p;
+
+ switch (NS_PTR_TO_INT32(entry->mKey)) {
+#define FRAME_ID(classname) \
+ case nsQueryFrame::classname##_id: \
+ p = &aArenaStats->FRAME_ID_STAT_FIELD(classname); \
+ break;
+#include "nsFrameIdList.h"
+#undef FRAME_ID
+ case eArenaObjectID_nsLineBox:
+ p = &aArenaStats->mLineBoxes;
+ break;
+ case eArenaObjectID_nsRuleNode:
+ p = &aArenaStats->mRuleNodes;
+ break;
+ case eArenaObjectID_nsStyleContext:
+ p = &aArenaStats->mStyleContexts;
+ break;
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ case eArenaObjectID_nsStyle##name_:
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ p = &aArenaStats->mStyleStructs;
+ break;
+ default:
+ continue;
+ }
+
+ *p += totalSize;
+ totalSizeInFreeLists += totalSize;
+ }
+
+ aArenaStats->mOther += mallocSize - totalSizeInFreeLists;
+}
diff --git a/layout/base/nsPresArena.h b/layout/base/nsPresArena.h
new file mode 100644
index 000000000..32407541e
--- /dev/null
+++ b/layout/base/nsPresArena.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+/* arena allocation for the frame tree and closely-related objects */
+
+#ifndef nsPresArena_h___
+#define nsPresArena_h___
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/ArenaRefPtr.h"
+#include "mozilla/MemoryChecking.h" // Note: Do not remove this, needed for MOZ_HAVE_MEM_CHECKS below
+#include "mozilla/MemoryReporting.h"
+#include <stdint.h>
+#include "nscore.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "plarena.h"
+
+struct nsArenaMemoryStats;
+
+class nsPresArena {
+public:
+ nsPresArena();
+ ~nsPresArena();
+
+ /**
+ * Pool allocation with recycler lists indexed by object size, aSize.
+ */
+ void* AllocateBySize(size_t aSize)
+ {
+ return Allocate(uint32_t(aSize) |
+ uint32_t(mozilla::eArenaObjectID_NON_OBJECT_MARKER), aSize);
+ }
+ void FreeBySize(size_t aSize, void* aPtr)
+ {
+ Free(uint32_t(aSize) |
+ uint32_t(mozilla::eArenaObjectID_NON_OBJECT_MARKER), aPtr);
+ }
+
+ /**
+ * Pool allocation with recycler lists indexed by frame-type ID.
+ * Every aID must always be used with the same object size, aSize.
+ */
+ void* AllocateByFrameID(nsQueryFrame::FrameIID aID, size_t aSize)
+ {
+ return Allocate(aID, aSize);
+ }
+ void FreeByFrameID(nsQueryFrame::FrameIID aID, void* aPtr)
+ {
+ Free(aID, aPtr);
+ }
+
+ /**
+ * Pool allocation with recycler lists indexed by object-type ID (see above).
+ * Every aID must always be used with the same object size, aSize.
+ */
+ void* AllocateByObjectID(mozilla::ArenaObjectID aID, size_t aSize)
+ {
+ return Allocate(aID, aSize);
+ }
+ void FreeByObjectID(mozilla::ArenaObjectID aID, void* aPtr)
+ {
+ Free(aID, aPtr);
+ }
+
+ /**
+ * Register an ArenaRefPtr to be cleared when this arena is about to
+ * be destroyed.
+ *
+ * (Defined in ArenaRefPtrInlines.h.)
+ *
+ * @param aPtr The ArenaRefPtr to clear.
+ * @param aObjectID The ArenaObjectID value that uniquely identifies
+ * the type of object the ArenaRefPtr holds.
+ */
+ template<typename T>
+ void RegisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr);
+
+ /**
+ * Deregister an ArenaRefPtr that was previously registered with
+ * RegisterArenaRefPtr.
+ */
+ template<typename T>
+ void DeregisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr)
+ {
+ MOZ_ASSERT(mArenaRefPtrs.Contains(aPtr));
+ mArenaRefPtrs.Remove(aPtr);
+ }
+
+ /**
+ * Clears all currently registered ArenaRefPtrs. This will be called during
+ * the destructor, but can be called by users of nsPresArena who want to
+ * ensure arena-allocated objects are released earlier.
+ */
+ void ClearArenaRefPtrs();
+
+ /**
+ * Clears all currently registered ArenaRefPtrs for the given ArenaObjectID.
+ * This is called when we reconstruct the rule tree so that style contexts
+ * pointing into the old rule tree aren't released afterwards, triggering an
+ * assertion in ~nsStyleContext.
+ */
+ void ClearArenaRefPtrs(mozilla::ArenaObjectID aObjectID);
+
+ /**
+ * Increment aArenaStats with sizes of interesting objects allocated in this
+ * arena and its mOther field with the size of everything else.
+ */
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ nsArenaMemoryStats* aArenaStats);
+
+private:
+ void* Allocate(uint32_t aCode, size_t aSize);
+ void Free(uint32_t aCode, void* aPtr);
+
+ inline void ClearArenaRefPtrWithoutDeregistering(
+ void* aPtr,
+ mozilla::ArenaObjectID aObjectID);
+
+ // All keys to this hash table fit in 32 bits (see below) so we do not
+ // bother actually hashing them.
+ class FreeList : public PLDHashEntryHdr
+ {
+ public:
+ typedef uint32_t KeyType;
+ nsTArray<void *> mEntries;
+ size_t mEntrySize;
+ size_t mEntriesEverAllocated;
+
+ typedef const void* KeyTypePointer;
+ KeyTypePointer mKey;
+
+ explicit FreeList(KeyTypePointer aKey)
+ : mEntrySize(0), mEntriesEverAllocated(0), mKey(aKey) {}
+ // Default copy constructor and destructor are ok.
+
+ bool KeyEquals(KeyTypePointer const aKey) const
+ { return mKey == aKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey)
+ { return NS_INT32_TO_PTR(aKey); }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ { return NS_PTR_TO_INT32(aKey); }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ { return mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf); }
+
+ enum { ALLOW_MEMMOVE = false };
+ };
+
+ nsTHashtable<FreeList> mFreeLists;
+ PLArenaPool mPool;
+ nsDataHashtable<nsPtrHashKey<void>, mozilla::ArenaObjectID> mArenaRefPtrs;
+};
+
+#endif
diff --git a/layout/base/nsPresArenaObjectList.h b/layout/base/nsPresArenaObjectList.h
new file mode 100644
index 000000000..c226aa3b4
--- /dev/null
+++ b/layout/base/nsPresArenaObjectList.h
@@ -0,0 +1,72 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a list of all types that can be allocated in an nsPresArena, for
+ preprocessing */
+
+#ifdef STYLE_STRUCT
+#error Sorry nsPresArenaObjectList.h needs to use STYLE_STRUCT!
+#endif
+
+#ifdef PRES_ARENA_OBJECT
+#if defined(PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT) || \
+ defined(PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT)
+#error Must not define PRES_ARENA_OBJECT along with \
+ PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT or \
+ PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT.
+#endif
+#define PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(name_) PRES_ARENA_OBJECT(name_)
+#define PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(name_) PRES_ARENA_OBJECT(name_)
+#define DEFINED_PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#define DEFINED_PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#endif
+
+#ifndef PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#define PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(name_) /* nothing */
+#define DEFINED_PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#endif
+
+#ifndef PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#define PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(name_) /* nothing */
+#define DEFINED_PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#endif
+
+// Use PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT to mention an nsPresArena-
+// allocated object that does not support ArenaRefPtr, and use
+// PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT to mention one that does.
+//
+// All PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT classes must be #included into
+// nsPresArena.cpp.
+//
+// Files including nsPresArenaObjectList.h can either define one or both
+// of PRES_ARENA_OBJECT_{WITH,WITHOUT}_ARENAREFPTR_SUPPORT to capture those
+// classes separately, or PRES_ARENA_OBJECT to capture all nsPresArena-
+// allocated classes.
+
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsLineBox)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsRuleNode)
+PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(nsStyleContext)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsInheritedStyleData)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsResetStyleData)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsConditionalResetStyleData)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsConditionalResetStyleDataEntry)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsFrameList)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(CustomCounterStyle)
+PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(DependentBuiltinCounterStyle)
+
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT(nsStyle##name_)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+#ifdef DEFINED_PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#undef PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#undef DEFINED_PRES_ARENA_OBJECT_WITHOUT_ARENAREFPTR_SUPPORT
+#endif
+
+#ifdef DEFINED_PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#undef PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#undef DEFINED_PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
+#endif
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
new file mode 100644
index 000000000..b27e6d0e3
--- /dev/null
+++ b/layout/base/nsPresContext.cpp
@@ -0,0 +1,3159 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a presentation of a document, part 1 */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+
+#include "base/basictypes.h"
+
+#include "nsCOMPtr.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsDocShell.h"
+#include "nsIContentViewer.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIDocument.h"
+#include "nsIPrintSettings.h"
+#include "nsILanguageAtomService.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsThreadUtils.h"
+#include "nsFrameManager.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "SurfaceCacheUtils.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleNode.h"
+#include "gfxPlatform.h"
+#include "nsCSSRules.h"
+#include "nsFontFaceLoader.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EventListenerManager.h"
+#include "prenv.h"
+#include "nsPluginFrame.h"
+#include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
+#include "CounterStyleManager.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "nsIMessageManager.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "nsSMILAnimationController.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsRefreshDriver.h"
+#include "Layers.h"
+#include "LayerUserData.h"
+#include "ClientLayerManager.h"
+#include "mozilla/dom/NotifyPaintEvent.h"
+#include "gfxPrefs.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsFrameLoader.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "nsContentUtils.h"
+#include "nsPIWindowRoot.h"
+#include "mozilla/Preferences.h"
+#include "gfxTextRun.h"
+#include "nsFontFaceUtils.h"
+#include "nsLayoutStylesheetCache.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
+#endif
+
+
+// Needed for Start/Stop of Image Animation
+#include "imgIContainer.h"
+#include "nsIImageLoadingContent.h"
+
+#include "nsCSSParser.h"
+#include "nsBidiUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/URL.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+uint8_t gNotifySubDocInvalidationData;
+
+/**
+ * Layer UserData for ContainerLayers that want to be notified
+ * of local invalidations of them and their descendant layers.
+ * Pass a callback to ComputeDifferences to have these called.
+ */
+class ContainerLayerPresContext : public LayerUserData {
+public:
+ nsPresContext* mPresContext;
+};
+
+namespace {
+
+class CharSetChangingRunnable : public Runnable
+{
+public:
+ CharSetChangingRunnable(nsPresContext* aPresContext,
+ const nsCString& aCharSet)
+ : mPresContext(aPresContext),
+ mCharSet(aCharSet)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mPresContext->DoChangeCharSet(mCharSet);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPresContext> mPresContext;
+ nsCString mCharSet;
+};
+
+} // namespace
+
+nscolor
+nsPresContext::MakeColorPref(const nsString& aColor)
+{
+ nsCSSParser parser;
+ nsCSSValue value;
+ if (!parser.ParseColorString(aColor, nullptr, 0, value)) {
+ // Any better choices?
+ return NS_RGB(0, 0, 0);
+ }
+
+ nscolor color;
+ return nsRuleNode::ComputeColor(value, this, nullptr, color)
+ ? color
+ : NS_RGB(0, 0, 0);
+}
+
+bool
+nsPresContext::IsDOMPaintEventPending()
+{
+ if (mFireAfterPaintEvents) {
+ return true;
+ }
+ nsRootPresContext* drpc = GetRootPresContext();
+ if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
+ // Since we're promising that there will be a MozAfterPaint event
+ // fired, we record an empty invalidation in case display list
+ // invalidation doesn't invalidate anything further.
+ NotifyInvalidation(nsRect(0, 0, 0, 0), 0);
+ NS_ASSERTION(mFireAfterPaintEvents, "Why aren't we planning to fire the event?");
+ return true;
+ }
+ return false;
+}
+
+void
+nsPresContext::PrefChangedCallback(const char* aPrefName, void* instance_data)
+{
+ RefPtr<nsPresContext> presContext =
+ static_cast<nsPresContext*>(instance_data);
+
+ NS_ASSERTION(nullptr != presContext, "bad instance data");
+ if (nullptr != presContext) {
+ presContext->PreferenceChanged(aPrefName);
+ }
+}
+
+void
+nsPresContext::PrefChangedUpdateTimerCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsPresContext* presContext = (nsPresContext*)aClosure;
+ NS_ASSERTION(presContext != nullptr, "bad instance data");
+ if (presContext)
+ presContext->UpdateAfterPreferencesChanged();
+}
+
+static bool
+IsVisualCharset(const nsCString& aCharset)
+{
+ if (aCharset.LowerCaseEqualsLiteral("ibm862") // Hebrew
+ || aCharset.LowerCaseEqualsLiteral("iso-8859-8") ) { // Hebrew
+ return true; // visual text type
+ }
+ else {
+ return false; // logical text type
+ }
+}
+
+ // NOTE! nsPresContext::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+nsPresContext::nsPresContext(nsIDocument* aDocument, nsPresContextType aType)
+ : mType(aType), mDocument(aDocument), mBaseMinFontSize(0),
+ mTextZoom(1.0), mFullZoom(1.0), mOverrideDPPX(0.0),
+ mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)),
+ mPageSize(-1, -1), mPPScale(1.0f),
+ mViewportStyleScrollbar(NS_STYLE_OVERFLOW_AUTO, NS_STYLE_OVERFLOW_AUTO),
+ mImageAnimationModePref(imgIContainer::kNormalAnimMode),
+ mAllInvalidated(false),
+ mPaintFlashing(false), mPaintFlashingInitialized(false)
+{
+ // NOTE! nsPresContext::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+ mDoScaledTwips = true;
+
+ SetBackgroundImageDraw(true); // always draw the background
+ SetBackgroundColorDraw(true);
+
+ mBackgroundColor = NS_RGB(0xFF, 0xFF, 0xFF);
+
+ mUseDocumentColors = true;
+ mUseDocumentFonts = true;
+
+ // the minimum font-size is unconstrained by default
+
+ mLinkColor = NS_RGB(0x00, 0x00, 0xEE);
+ mActiveLinkColor = NS_RGB(0xEE, 0x00, 0x00);
+ mVisitedLinkColor = NS_RGB(0x55, 0x1A, 0x8B);
+ mUnderlineLinks = true;
+ mSendAfterPaintToContent = false;
+
+ mFocusTextColor = mDefaultColor;
+ mFocusBackgroundColor = mBackgroundColor;
+ mFocusRingWidth = 1;
+
+ mBodyTextColor = mDefaultColor;
+
+ if (aType == eContext_Galley) {
+ mMedium = nsGkAtoms::screen;
+ } else {
+ mMedium = nsGkAtoms::print;
+ mPaginated = true;
+ }
+ mMediaEmulated = mMedium;
+
+ if (!IsDynamic()) {
+ mImageAnimationMode = imgIContainer::kDontAnimMode;
+ mNeverAnimate = true;
+ } else {
+ mImageAnimationMode = imgIContainer::kNormalAnimMode;
+ mNeverAnimate = false;
+ }
+ NS_ASSERTION(mDocument, "Null document");
+
+ mCounterStylesDirty = true;
+
+ // if text perf logging enabled, init stats struct
+ if (MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_textperf), LogLevel::Warning)) {
+ mTextPerf = new gfxTextPerfMetrics();
+ }
+
+ if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+ mMissingFonts = new gfxMissingFontRecorder();
+ }
+}
+
+void
+nsPresContext::Destroy()
+{
+ if (mEventManager) {
+ // unclear if these are needed, but can't hurt
+ mEventManager->NotifyDestroyPresContext(this);
+ mEventManager->SetPresContext(nullptr);
+ mEventManager = nullptr;
+ }
+
+ if (mPrefChangedTimer)
+ {
+ mPrefChangedTimer->Cancel();
+ mPrefChangedTimer = nullptr;
+ }
+
+ // Unregister preference callbacks
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "font.",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.display.",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.underline_anchors",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.anchor_color",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.active_color",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.visited_color",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "image.animation_mode",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "bidi.",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "dom.send_after_paint_to_content",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "gfx.font_rendering.",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "layout.css.dpi",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "layout.css.devPixelsPerPx",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "nglayout.debug.paint_flashing",
+ this);
+ Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
+ "nglayout.debug.paint_flashing_chrome",
+ this);
+
+ mRefreshDriver = nullptr;
+}
+
+nsPresContext::~nsPresContext()
+{
+ NS_PRECONDITION(!mShell, "Presshell forgot to clear our mShell pointer");
+ DetachShell();
+
+ Destroy();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPresContext)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease())
+
+void
+nsPresContext::LastRelease()
+{
+ if (IsRoot()) {
+ static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
+ }
+ if (mMissingFonts) {
+ mMissingFonts->Clear();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimationManager);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument);
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDeviceContext); // not xpcom
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEffectCompositor);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventManager);
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mLanguage); // an atom
+
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTheme); // a service
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLangService); // a service
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrintSettings);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrefChangedTimer);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsPresContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimationManager);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeviceContext); // worth bothering?
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEffectCompositor);
+ // NS_RELEASE(tmp->mLanguage); // an atom
+ // NS_IMPL_CYCLE_COLLECTION_UNLINK(mTheme); // a service
+ // NS_IMPL_CYCLE_COLLECTION_UNLINK(mLangService); // a service
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrintSettings);
+
+ tmp->Destroy();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+// whether no native theme service exists;
+// if this gets set to true, we'll stop asking for it.
+static bool sNoTheme = false;
+
+// Set to true when LookAndFeelChanged needs to be called. This is used
+// because the look and feel is a service, so there's no need to notify it from
+// more than one prescontext.
+static bool sLookAndFeelChanged;
+
+// Set to true when ThemeChanged needs to be called on mTheme. This is used
+// because mTheme is a service, so there's no need to notify it from more than
+// one prescontext.
+static bool sThemeChanged;
+
+void
+nsPresContext::GetDocumentColorPreferences()
+{
+ // Make sure the preferences are initialized. In the normal run,
+ // they would already be, because gfxPlatform would have been created,
+ // but in some reference tests, that is not the case.
+ gfxPrefs::GetSingleton();
+
+ int32_t useAccessibilityTheme = 0;
+ bool usePrefColors = true;
+ bool isChromeDocShell = false;
+ static int32_t sDocumentColorsSetting;
+ static bool sDocumentColorsSettingPrefCached = false;
+ if (!sDocumentColorsSettingPrefCached) {
+ sDocumentColorsSettingPrefCached = true;
+ Preferences::AddIntVarCache(&sDocumentColorsSetting,
+ "browser.display.document_color_use",
+ 0);
+ }
+
+ nsIDocument* doc = mDocument->GetDisplayDocument();
+ if (doc && doc->GetDocShell()) {
+ isChromeDocShell = nsIDocShellTreeItem::typeChrome ==
+ doc->GetDocShell()->ItemType();
+ } else {
+ nsCOMPtr<nsIDocShellTreeItem> docShell(mContainer);
+ if (docShell) {
+ isChromeDocShell = nsIDocShellTreeItem::typeChrome == docShell->ItemType();
+ }
+ }
+
+ mIsChromeOriginImage = mDocument->IsBeingUsedAsImage() &&
+ IsChromeURI(mDocument->GetDocumentURI());
+
+ if (isChromeDocShell || mIsChromeOriginImage) {
+ usePrefColors = false;
+ } else {
+ useAccessibilityTheme =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_UseAccessibilityTheme, 0);
+ usePrefColors = !useAccessibilityTheme;
+ }
+ if (usePrefColors) {
+ usePrefColors =
+ !Preferences::GetBool("browser.display.use_system_colors", false);
+ }
+
+ if (usePrefColors) {
+ nsAdoptingString colorStr =
+ Preferences::GetString("browser.display.foreground_color");
+
+ if (!colorStr.IsEmpty()) {
+ mDefaultColor = MakeColorPref(colorStr);
+ }
+
+ colorStr = Preferences::GetString("browser.display.background_color");
+
+ if (!colorStr.IsEmpty()) {
+ mBackgroundColor = MakeColorPref(colorStr);
+ }
+ }
+ else {
+ mDefaultColor =
+ LookAndFeel::GetColor(LookAndFeel::eColorID_WindowForeground,
+ NS_RGB(0x00, 0x00, 0x00));
+ mBackgroundColor =
+ LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground,
+ NS_RGB(0xFF, 0xFF, 0xFF));
+ }
+
+ // Wherever we got the default background color from, ensure it is
+ // opaque.
+ mBackgroundColor = NS_ComposeColors(NS_RGB(0xFF, 0xFF, 0xFF),
+ mBackgroundColor);
+
+
+ // Now deal with the pref:
+ // 0 = default: always, except in high contrast mode
+ // 1 = always
+ // 2 = never
+ if (sDocumentColorsSetting == 1 || mDocument->IsBeingUsedAsImage()) {
+ mUseDocumentColors = true;
+ } else if (sDocumentColorsSetting == 2) {
+ mUseDocumentColors = isChromeDocShell || mIsChromeOriginImage;
+ } else {
+ MOZ_ASSERT(!useAccessibilityTheme ||
+ !(isChromeDocShell || mIsChromeOriginImage),
+ "The accessibility theme should only be on for non-chrome");
+ mUseDocumentColors = !useAccessibilityTheme;
+ }
+}
+
+void
+nsPresContext::GetUserPreferences()
+{
+ if (!GetPresShell()) {
+ // No presshell means nothing to do here. We'll do this when we
+ // get a presshell.
+ return;
+ }
+
+ mAutoQualityMinFontSizePixelsPref =
+ Preferences::GetInt("browser.display.auto_quality_min_font_size");
+
+ // * document colors
+ GetDocumentColorPreferences();
+
+ mSendAfterPaintToContent =
+ Preferences::GetBool("dom.send_after_paint_to_content",
+ mSendAfterPaintToContent);
+
+ // * link colors
+ mUnderlineLinks =
+ Preferences::GetBool("browser.underline_anchors", mUnderlineLinks);
+
+ nsAdoptingString colorStr = Preferences::GetString("browser.anchor_color");
+
+ if (!colorStr.IsEmpty()) {
+ mLinkColor = MakeColorPref(colorStr);
+ }
+
+ colorStr = Preferences::GetString("browser.active_color");
+
+ if (!colorStr.IsEmpty()) {
+ mActiveLinkColor = MakeColorPref(colorStr);
+ }
+
+ colorStr = Preferences::GetString("browser.visited_color");
+
+ if (!colorStr.IsEmpty()) {
+ mVisitedLinkColor = MakeColorPref(colorStr);
+ }
+
+ mUseFocusColors =
+ Preferences::GetBool("browser.display.use_focus_colors", mUseFocusColors);
+
+ mFocusTextColor = mDefaultColor;
+ mFocusBackgroundColor = mBackgroundColor;
+
+ colorStr = Preferences::GetString("browser.display.focus_text_color");
+
+ if (!colorStr.IsEmpty()) {
+ mFocusTextColor = MakeColorPref(colorStr);
+ }
+
+ colorStr = Preferences::GetString("browser.display.focus_background_color");
+
+ if (!colorStr.IsEmpty()) {
+ mFocusBackgroundColor = MakeColorPref(colorStr);
+ }
+
+ mFocusRingWidth =
+ Preferences::GetInt("browser.display.focus_ring_width", mFocusRingWidth);
+
+ mFocusRingOnAnything =
+ Preferences::GetBool("browser.display.focus_ring_on_anything",
+ mFocusRingOnAnything);
+
+ mFocusRingStyle =
+ Preferences::GetInt("browser.display.focus_ring_style", mFocusRingStyle);
+
+ mBodyTextColor = mDefaultColor;
+
+ // * use fonts?
+ mUseDocumentFonts =
+ Preferences::GetInt("browser.display.use_document_fonts") != 0;
+
+ mPrefScrollbarSide = Preferences::GetInt("layout.scrollbar.side");
+
+ mLangGroupFontPrefs.Reset();
+ StaticPresData::Get()->ResetCachedFontPrefs();
+
+ // * image animation
+ const nsAdoptingCString& animatePref =
+ Preferences::GetCString("image.animation_mode");
+ if (animatePref.EqualsLiteral("normal"))
+ mImageAnimationModePref = imgIContainer::kNormalAnimMode;
+ else if (animatePref.EqualsLiteral("none"))
+ mImageAnimationModePref = imgIContainer::kDontAnimMode;
+ else if (animatePref.EqualsLiteral("once"))
+ mImageAnimationModePref = imgIContainer::kLoopOnceAnimMode;
+ else // dynamic change to invalid value should act like it does initially
+ mImageAnimationModePref = imgIContainer::kNormalAnimMode;
+
+ uint32_t bidiOptions = GetBidi();
+
+ int32_t prefInt =
+ Preferences::GetInt(IBMBIDI_TEXTDIRECTION_STR,
+ GET_BIDI_OPTION_DIRECTION(bidiOptions));
+ SET_BIDI_OPTION_DIRECTION(bidiOptions, prefInt);
+ mPrefBidiDirection = prefInt;
+
+ prefInt =
+ Preferences::GetInt(IBMBIDI_TEXTTYPE_STR,
+ GET_BIDI_OPTION_TEXTTYPE(bidiOptions));
+ SET_BIDI_OPTION_TEXTTYPE(bidiOptions, prefInt);
+
+ prefInt =
+ Preferences::GetInt(IBMBIDI_NUMERAL_STR,
+ GET_BIDI_OPTION_NUMERAL(bidiOptions));
+ SET_BIDI_OPTION_NUMERAL(bidiOptions, prefInt);
+
+ // We don't need to force reflow: either we are initializing a new
+ // prescontext or we are being called from UpdateAfterPreferencesChanged()
+ // which triggers a reflow anyway.
+ SetBidi(bidiOptions, false);
+}
+
+void
+nsPresContext::InvalidatePaintedLayers()
+{
+ if (!mShell)
+ return;
+ nsIFrame* rootFrame = mShell->FrameManager()->GetRootFrame();
+ if (rootFrame) {
+ // FrameLayerBuilder caches invalidation-related values that depend on the
+ // appunits-per-dev-pixel ratio, so ensure that all PaintedLayer drawing
+ // is completely flushed.
+ rootFrame->InvalidateFrameSubtree();
+ }
+}
+
+void
+nsPresContext::AppUnitsPerDevPixelChanged()
+{
+ InvalidatePaintedLayers();
+
+ if (mDeviceContext) {
+ mDeviceContext->FlushFontCache();
+ }
+
+ if (HasCachedStyleData()) {
+ // All cached style data must be recomputed.
+ MediaFeatureValuesChanged(eRestyle_ForceDescendants, NS_STYLE_HINT_REFLOW);
+ }
+
+ mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel();
+}
+
+void
+nsPresContext::PreferenceChanged(const char* aPrefName)
+{
+ nsDependentCString prefName(aPrefName);
+ if (prefName.EqualsLiteral("layout.css.dpi") ||
+ prefName.EqualsLiteral("layout.css.devPixelsPerPx")) {
+ int32_t oldAppUnitsPerDevPixel = AppUnitsPerDevPixel();
+ if (mDeviceContext->CheckDPIChange() && mShell) {
+ nsCOMPtr<nsIPresShell> shell = mShell;
+ // Re-fetch the view manager's window dimensions in case there's a deferred
+ // resize which hasn't affected our mVisibleArea yet
+ nscoord oldWidthAppUnits, oldHeightAppUnits;
+ RefPtr<nsViewManager> vm = shell->GetViewManager();
+ if (!vm) {
+ return;
+ }
+ vm->GetWindowDimensions(&oldWidthAppUnits, &oldHeightAppUnits);
+ float oldWidthDevPixels = oldWidthAppUnits/oldAppUnitsPerDevPixel;
+ float oldHeightDevPixels = oldHeightAppUnits/oldAppUnitsPerDevPixel;
+
+ nscoord width = NSToCoordRound(oldWidthDevPixels*AppUnitsPerDevPixel());
+ nscoord height = NSToCoordRound(oldHeightDevPixels*AppUnitsPerDevPixel());
+ vm->SetWindowDimensions(width, height);
+
+ AppUnitsPerDevPixelChanged();
+ }
+ return;
+ }
+ if (prefName.EqualsLiteral(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+ if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+ if (!mMissingFonts) {
+ mMissingFonts = new gfxMissingFontRecorder();
+ // trigger reflow to detect missing fonts on the current page
+ mPrefChangePendingNeedsReflow = true;
+ }
+ } else {
+ if (mMissingFonts) {
+ mMissingFonts->Clear();
+ }
+ mMissingFonts = nullptr;
+ }
+ }
+ if (StringBeginsWith(prefName, NS_LITERAL_CSTRING("font."))) {
+ // Changes to font family preferences don't change anything in the
+ // computed style data, so the style system won't generate a reflow
+ // hint for us. We need to do that manually.
+
+ // FIXME We could probably also handle changes to
+ // browser.display.auto_quality_min_font_size here, but that
+ // probably also requires clearing the text run cache, so don't
+ // bother (yet, anyway).
+ mPrefChangePendingNeedsReflow = true;
+ }
+ if (StringBeginsWith(prefName, NS_LITERAL_CSTRING("bidi."))) {
+ // Changes to bidi prefs need to trigger a reflow (see bug 443629)
+ mPrefChangePendingNeedsReflow = true;
+
+ // Changes to bidi.numeral also needs to empty the text run cache.
+ // This is handled in gfxTextRunWordCache.cpp.
+ }
+ if (StringBeginsWith(prefName, NS_LITERAL_CSTRING("gfx.font_rendering."))) {
+ // Changes to font_rendering prefs need to trigger a reflow
+ mPrefChangePendingNeedsReflow = true;
+ }
+ // we use a zero-delay timer to coalesce multiple pref updates
+ if (!mPrefChangedTimer)
+ {
+ // We will end up calling InvalidatePreferenceSheets one from each pres
+ // context, but all it's doing is clearing its cached sheet pointers,
+ // so it won't be wastefully recreating the sheet multiple times.
+ // The first pres context that has its mPrefChangedTimer called will
+ // be the one to cause the reconstruction of the pref style sheet.
+ nsLayoutStylesheetCache::InvalidatePreferenceSheets();
+ mPrefChangedTimer = CreateTimer(PrefChangedUpdateTimerCallback, 0);
+ if (!mPrefChangedTimer) {
+ return;
+ }
+ }
+ if (prefName.EqualsLiteral("nglayout.debug.paint_flashing") ||
+ prefName.EqualsLiteral("nglayout.debug.paint_flashing_chrome")) {
+ mPaintFlashingInitialized = false;
+ return;
+ }
+}
+
+void
+nsPresContext::UpdateAfterPreferencesChanged()
+{
+ mPrefChangedTimer = nullptr;
+
+ if (!mContainer) {
+ // Delay updating until there is a container
+ mNeedsPrefUpdate = true;
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShell(mContainer);
+ if (docShell && nsIDocShellTreeItem::typeChrome == docShell->ItemType()) {
+ return;
+ }
+
+ // Initialize our state from the user preferences
+ GetUserPreferences();
+
+ // update the presShell: tell it to set the preference style rules up
+ if (mShell) {
+ mShell->UpdatePreferenceStyles();
+ }
+
+ InvalidatePaintedLayers();
+ mDeviceContext->FlushFontCache();
+
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mPrefChangePendingNeedsReflow) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ // Preferences require rerunning selector matching because we rebuild
+ // the pref style sheet for some preference changes.
+ RebuildAllStyleData(hint, eRestyle_Subtree);
+}
+
+nsresult
+nsPresContext::Init(nsDeviceContext* aDeviceContext)
+{
+ NS_ASSERTION(!mInitialized, "attempt to reinit pres context");
+ NS_ENSURE_ARG(aDeviceContext);
+
+ mDeviceContext = aDeviceContext;
+
+ if (mDeviceContext->SetFullZoom(mFullZoom))
+ mDeviceContext->FlushFontCache();
+ mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel();
+
+ mEventManager = new mozilla::EventStateManager();
+
+ mEffectCompositor = new mozilla::EffectCompositor(this);
+ mTransitionManager = new nsTransitionManager(this);
+ mAnimationManager = new nsAnimationManager(this);
+
+ if (mDocument->GetDisplayDocument()) {
+ NS_ASSERTION(mDocument->GetDisplayDocument()->GetShell() &&
+ mDocument->GetDisplayDocument()->GetShell()->GetPresContext(),
+ "Why are we being initialized?");
+ mRefreshDriver = mDocument->GetDisplayDocument()->GetShell()->
+ GetPresContext()->RefreshDriver();
+ } else {
+ nsIDocument* parent = mDocument->GetParentDocument();
+ // Unfortunately, sometimes |parent| here has no presshell because
+ // printing screws up things. Assert that in other cases it does,
+ // but whenever the shell is null just fall back on using our own
+ // refresh driver.
+ NS_ASSERTION(!parent || mDocument->IsStaticDocument() || parent->GetShell(),
+ "How did we end up with a presshell if our parent doesn't "
+ "have one?");
+ if (parent && parent->GetShell()) {
+ NS_ASSERTION(parent->GetShell()->GetPresContext(),
+ "How did we get a presshell?");
+
+ // We don't have our container set yet at this point
+ nsCOMPtr<nsIDocShellTreeItem> ourItem = mDocument->GetDocShell();
+ if (ourItem) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ ourItem->GetSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ Element* containingElement =
+ parent->FindContentForSubDocument(mDocument);
+ if (!containingElement->IsXULElement() ||
+ !containingElement->
+ HasAttr(kNameSpaceID_None,
+ nsGkAtoms::forceOwnRefreshDriver)) {
+ mRefreshDriver = parent->GetShell()->GetPresContext()->RefreshDriver();
+ }
+ }
+ }
+ }
+
+ if (!mRefreshDriver) {
+ mRefreshDriver = new nsRefreshDriver(this);
+ }
+ }
+
+ mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
+
+ // Register callbacks so we're notified when the preferences change
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "font.",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.display.",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.underline_anchors",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.anchor_color",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.active_color",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "browser.visited_color",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "image.animation_mode",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "bidi.",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "dom.send_after_paint_to_content",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "gfx.font_rendering.",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "layout.css.dpi",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "layout.css.devPixelsPerPx",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "nglayout.debug.paint_flashing",
+ this);
+ Preferences::RegisterCallback(nsPresContext::PrefChangedCallback,
+ "nglayout.debug.paint_flashing_chrome",
+ this);
+
+ nsresult rv = mEventManager->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEventManager->SetPresContext(this);
+
+#ifdef RESTYLE_LOGGING
+ mRestyleLoggingEnabled = RestyleManager::RestyleLoggingInitiallyEnabled();
+#endif
+
+#ifdef DEBUG
+ mInitialized = true;
+#endif
+
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THIN] = CSSPixelsToAppUnits(1);
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_MEDIUM] = CSSPixelsToAppUnits(3);
+ mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THICK] = CSSPixelsToAppUnits(5);
+
+ return NS_OK;
+}
+
+// Note: We don't hold a reference on the shell; it has a reference to
+// us
+void
+nsPresContext::AttachShell(nsIPresShell* aShell, StyleBackendType aBackendType)
+{
+ MOZ_ASSERT(!mShell);
+ mShell = aShell;
+
+ if (aBackendType == StyleBackendType::Servo) {
+ mRestyleManager = new ServoRestyleManager(this);
+ } else {
+ // Since RestyleManager is also the name of a method of nsPresContext,
+ // it is necessary to prefix the class with the mozilla namespace
+ // here.
+ mRestyleManager = new mozilla::RestyleManager(this);
+ }
+
+ // Since CounterStyleManager is also the name of a method of
+ // nsPresContext, it is necessary to prefix the class with the mozilla
+ // namespace here.
+ mCounterStyleManager = new mozilla::CounterStyleManager(this);
+
+ nsIDocument *doc = mShell->GetDocument();
+ NS_ASSERTION(doc, "expect document here");
+ if (doc) {
+ // Have to update PresContext's mDocument before calling any other methods.
+ mDocument = doc;
+ }
+ // Initialize our state from the user preferences, now that we
+ // have a presshell, and hence a document.
+ GetUserPreferences();
+
+ if (doc) {
+ nsIURI *docURI = doc->GetDocumentURI();
+
+ if (IsDynamic() && docURI) {
+ bool isChrome = false;
+ bool isRes = false;
+ docURI->SchemeIs("chrome", &isChrome);
+ docURI->SchemeIs("resource", &isRes);
+
+ if (!isChrome && !isRes)
+ mImageAnimationMode = mImageAnimationModePref;
+ else
+ mImageAnimationMode = imgIContainer::kNormalAnimMode;
+ }
+
+ if (mLangService) {
+ doc->AddCharSetObserver(this);
+ UpdateCharSet(doc->GetDocumentCharacterSet());
+ }
+ }
+}
+
+void
+nsPresContext::DetachShell()
+{
+ // Remove ourselves as the charset observer from the shell's doc, because
+ // this shell may be going away for good.
+ nsIDocument *doc = mShell ? mShell->GetDocument() : nullptr;
+ if (doc) {
+ doc->RemoveCharSetObserver(this);
+ }
+
+ // The counter style manager's destructor needs to deallocate with the
+ // presshell arena. Disconnect it before nulling out the shell.
+ //
+ // XXXbholley: Given recent refactorings, it probably makes more sense to
+ // just null our mShell at the bottom of this function. I'm leaving it
+ // this way to preserve the old ordering, but I doubt anything would break.
+ if (mCounterStyleManager) {
+ mCounterStyleManager->Disconnect();
+ mCounterStyleManager = nullptr;
+ }
+
+ mShell = nullptr;
+
+ if (mEffectCompositor) {
+ mEffectCompositor->Disconnect();
+ mEffectCompositor = nullptr;
+ }
+ if (mTransitionManager) {
+ mTransitionManager->Disconnect();
+ mTransitionManager = nullptr;
+ }
+ if (mAnimationManager) {
+ mAnimationManager->Disconnect();
+ mAnimationManager = nullptr;
+ }
+ if (mRestyleManager) {
+ mRestyleManager->Disconnect();
+ mRestyleManager = nullptr;
+ }
+ if (mRefreshDriver && mRefreshDriver->GetPresContext() == this) {
+ mRefreshDriver->Disconnect();
+ // Can't null out the refresh driver here.
+ }
+
+ if (IsRoot()) {
+ nsRootPresContext* thisRoot = static_cast<nsRootPresContext*>(this);
+
+ // Have to cancel our plugin geometry timer, because the
+ // callback for that depends on a non-null presshell.
+ thisRoot->CancelApplyPluginGeometryTimer();
+
+ // The did-paint timer also depends on a non-null pres shell.
+ thisRoot->CancelDidPaintTimer();
+ }
+}
+
+void
+nsPresContext::DoChangeCharSet(const nsCString& aCharSet)
+{
+ UpdateCharSet(aCharSet);
+ mDeviceContext->FlushFontCache();
+ RebuildAllStyleData(NS_STYLE_HINT_REFLOW, nsRestyleHint(0));
+}
+
+void
+nsPresContext::UpdateCharSet(const nsCString& aCharSet)
+{
+ if (mLangService) {
+ mLanguage = mLangService->LookupCharSet(aCharSet);
+ // this will be a language group (or script) code rather than a true language code
+
+ // bug 39570: moved from nsLanguageAtomService::LookupCharSet()
+ if (mLanguage == nsGkAtoms::Unicode) {
+ mLanguage = mLangService->GetLocaleLanguage();
+ }
+ mLangGroupFontPrefs.Reset();
+ }
+
+ switch (GET_BIDI_OPTION_TEXTTYPE(GetBidi())) {
+
+ case IBMBIDI_TEXTTYPE_LOGICAL:
+ SetVisualMode(false);
+ break;
+
+ case IBMBIDI_TEXTTYPE_VISUAL:
+ SetVisualMode(true);
+ break;
+
+ case IBMBIDI_TEXTTYPE_CHARSET:
+ default:
+ SetVisualMode(IsVisualCharset(aCharSet));
+ }
+}
+
+NS_IMETHODIMP
+nsPresContext::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!nsCRT::strcmp(aTopic, "charset")) {
+ RefPtr<CharSetChangingRunnable> runnable =
+ new CharSetChangingRunnable(this, NS_LossyConvertUTF16toASCII(aData));
+ return NS_DispatchToCurrentThread(runnable);
+ }
+
+ NS_WARNING("unrecognized topic in nsPresContext::Observe");
+ return NS_ERROR_FAILURE;
+}
+
+nsPresContext*
+nsPresContext::GetParentPresContext()
+{
+ nsIPresShell* shell = GetPresShell();
+ if (shell) {
+ nsViewManager* viewManager = shell->GetViewManager();
+ if (viewManager) {
+ nsView* view = viewManager->GetRootView();
+ if (view) {
+ view = view->GetParent(); // anonymous inner view
+ if (view) {
+ view = view->GetParent(); // subdocumentframe's view
+ if (view) {
+ nsIFrame* f = view->GetFrame();
+ if (f) {
+ return f->PresContext();
+ }
+ }
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsPresContext*
+nsPresContext::GetToplevelContentDocumentPresContext()
+{
+ if (IsChrome())
+ return nullptr;
+ nsPresContext* pc = this;
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent || parent->IsChrome())
+ return pc;
+ pc = parent;
+ }
+}
+
+nsIWidget*
+nsPresContext::GetNearestWidget(nsPoint* aOffset)
+{
+ NS_ENSURE_TRUE(mShell, nullptr);
+ nsIFrame* frame = mShell->GetRootFrame();
+ NS_ENSURE_TRUE(frame, nullptr);
+ return frame->GetView()->GetNearestWidget(aOffset);
+}
+
+nsIWidget*
+nsPresContext::GetRootWidget()
+{
+ NS_ENSURE_TRUE(mShell, nullptr);
+ nsViewManager* vm = mShell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ return widget.get();
+}
+
+// We may want to replace this with something faster, maybe caching the root prescontext
+nsRootPresContext*
+nsPresContext::GetRootPresContext()
+{
+ nsPresContext* pc = this;
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent)
+ break;
+ pc = parent;
+ }
+ return pc->IsRoot() ? static_cast<nsRootPresContext*>(pc) : nullptr;
+}
+
+void
+nsPresContext::CompatibilityModeChanged()
+{
+ if (!mShell) {
+ return;
+ }
+
+ nsIDocument* doc = mShell->GetDocument();
+ if (!doc) {
+ return;
+ }
+
+ if (doc->IsSVGDocument()) {
+ // SVG documents never load quirk.css.
+ return;
+ }
+
+ bool needsQuirkSheet = CompatibilityMode() == eCompatibility_NavQuirks;
+ if (mQuirkSheetAdded == needsQuirkSheet) {
+ return;
+ }
+
+ StyleSetHandle styleSet = mShell->StyleSet();
+ auto cache = nsLayoutStylesheetCache::For(styleSet->BackendType());
+ StyleSheet* sheet = cache->QuirkSheet();
+
+ if (needsQuirkSheet) {
+ // quirk.css needs to come after html.css; we just keep it at the end.
+ DebugOnly<nsresult> rv =
+ styleSet->AppendStyleSheet(SheetType::Agent, sheet);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "failed to insert quirk.css");
+ } else {
+ DebugOnly<nsresult> rv =
+ styleSet->RemoveStyleSheet(SheetType::Agent, sheet);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "failed to remove quirk.css");
+ }
+
+ mQuirkSheetAdded = needsQuirkSheet;
+}
+
+// Helper function for setting Anim Mode on image
+static void SetImgAnimModeOnImgReq(imgIRequest* aImgReq, uint16_t aMode)
+{
+ if (aImgReq) {
+ nsCOMPtr<imgIContainer> imgCon;
+ aImgReq->GetImage(getter_AddRefs(imgCon));
+ if (imgCon) {
+ imgCon->SetAnimationMode(aMode);
+ }
+ }
+}
+
+// IMPORTANT: Assumption is that all images for a Presentation
+// have the same Animation Mode (pavlov said this was OK)
+//
+// Walks content and set the animation mode
+// this is a way to turn on/off image animations
+void nsPresContext::SetImgAnimations(nsIContent *aParent, uint16_t aMode)
+{
+ nsCOMPtr<nsIImageLoadingContent> imgContent(do_QueryInterface(aParent));
+ if (imgContent) {
+ nsCOMPtr<imgIRequest> imgReq;
+ imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgReq));
+ SetImgAnimModeOnImgReq(imgReq, aMode);
+ }
+
+ uint32_t count = aParent->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ SetImgAnimations(aParent->GetChildAt(i), aMode);
+ }
+}
+
+void
+nsPresContext::SetSMILAnimations(nsIDocument *aDoc, uint16_t aNewMode,
+ uint16_t aOldMode)
+{
+ if (aDoc->HasAnimationController()) {
+ nsSMILAnimationController* controller = aDoc->GetAnimationController();
+ switch (aNewMode)
+ {
+ case imgIContainer::kNormalAnimMode:
+ case imgIContainer::kLoopOnceAnimMode:
+ if (aOldMode == imgIContainer::kDontAnimMode)
+ controller->Resume(nsSMILTimeContainer::PAUSE_USERPREF);
+ break;
+
+ case imgIContainer::kDontAnimMode:
+ if (aOldMode != imgIContainer::kDontAnimMode)
+ controller->Pause(nsSMILTimeContainer::PAUSE_USERPREF);
+ break;
+ }
+ }
+}
+
+void
+nsPresContext::SetImageAnimationModeInternal(uint16_t aMode)
+{
+ NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
+ aMode == imgIContainer::kDontAnimMode ||
+ aMode == imgIContainer::kLoopOnceAnimMode, "Wrong Animation Mode is being set!");
+
+ // Image animation mode cannot be changed when rendering to a printer.
+ if (!IsDynamic())
+ return;
+
+ // Now walk the content tree and set the animation mode
+ // on all the images.
+ if (mShell != nullptr) {
+ nsIDocument *doc = mShell->GetDocument();
+ if (doc) {
+ doc->StyleImageLoader()->SetAnimationMode(aMode);
+
+ Element *rootElement = doc->GetRootElement();
+ if (rootElement) {
+ SetImgAnimations(rootElement, aMode);
+ }
+ SetSMILAnimations(doc, aMode, mImageAnimationMode);
+ }
+ }
+
+ mImageAnimationMode = aMode;
+}
+
+void
+nsPresContext::SetImageAnimationModeExternal(uint16_t aMode)
+{
+ SetImageAnimationModeInternal(aMode);
+}
+
+already_AddRefed<nsIAtom>
+nsPresContext::GetContentLanguage() const
+{
+ nsAutoString language;
+ Document()->GetContentLanguage(language);
+ language.StripWhitespace();
+
+ // Content-Language may be a comma-separated list of language codes,
+ // in which case the HTML5 spec says to treat it as unknown
+ if (!language.IsEmpty() &&
+ !language.Contains(char16_t(','))) {
+ return NS_Atomize(language);
+ // NOTE: This does *not* count as an explicit language; in other
+ // words, it doesn't trigger language-specific hyphenation.
+ }
+ return nullptr;
+}
+
+void
+nsPresContext::SetFullZoom(float aZoom)
+{
+ if (!mShell || mFullZoom == aZoom) {
+ return;
+ }
+
+ // Re-fetch the view manager's window dimensions in case there's a deferred
+ // resize which hasn't affected our mVisibleArea yet
+ nscoord oldWidthAppUnits, oldHeightAppUnits;
+ mShell->GetViewManager()->GetWindowDimensions(&oldWidthAppUnits, &oldHeightAppUnits);
+ float oldWidthDevPixels = oldWidthAppUnits / float(mCurAppUnitsPerDevPixel);
+ float oldHeightDevPixels = oldHeightAppUnits / float(mCurAppUnitsPerDevPixel);
+ mDeviceContext->SetFullZoom(aZoom);
+
+ NS_ASSERTION(!mSuppressResizeReflow, "two zooms happening at the same time? impossible!");
+ mSuppressResizeReflow = true;
+
+ mFullZoom = aZoom;
+ mShell->GetViewManager()->
+ SetWindowDimensions(NSToCoordRound(oldWidthDevPixels * AppUnitsPerDevPixel()),
+ NSToCoordRound(oldHeightDevPixels * AppUnitsPerDevPixel()));
+
+ AppUnitsPerDevPixelChanged();
+
+ mSuppressResizeReflow = false;
+}
+
+void
+nsPresContext::SetOverrideDPPX(float aDPPX)
+{
+ mOverrideDPPX = aDPPX;
+
+ if (HasCachedStyleData()) {
+ MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+ }
+}
+
+gfxSize
+nsPresContext::ScreenSizeInchesForFontInflation(bool* aChanged)
+{
+ if (aChanged) {
+ *aChanged = false;
+ }
+
+ nsDeviceContext *dx = DeviceContext();
+ nsRect clientRect;
+ dx->GetClientRect(clientRect); // FIXME: GetClientRect looks expensive
+ float unitsPerInch = dx->AppUnitsPerPhysicalInch();
+ gfxSize deviceSizeInches(float(clientRect.width) / unitsPerInch,
+ float(clientRect.height) / unitsPerInch);
+
+ if (mLastFontInflationScreenSize == gfxSize(-1.0, -1.0)) {
+ mLastFontInflationScreenSize = deviceSizeInches;
+ }
+
+ if (deviceSizeInches != mLastFontInflationScreenSize && aChanged) {
+ *aChanged = true;
+ mLastFontInflationScreenSize = deviceSizeInches;
+ }
+
+ return deviceSizeInches;
+}
+
+static bool
+CheckOverflow(const nsStyleDisplay* aDisplay, ScrollbarStyles* aStyles)
+{
+ if (aDisplay->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE &&
+ aDisplay->mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_AUTO &&
+ aDisplay->mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
+ aDisplay->mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
+ aDisplay->mScrollSnapPointsX == nsStyleCoord(eStyleUnit_None) &&
+ aDisplay->mScrollSnapPointsY == nsStyleCoord(eStyleUnit_None) &&
+ !aDisplay->mScrollSnapDestination.mXPosition.mHasPercent &&
+ !aDisplay->mScrollSnapDestination.mYPosition.mHasPercent &&
+ aDisplay->mScrollSnapDestination.mXPosition.mLength == 0 &&
+ aDisplay->mScrollSnapDestination.mYPosition.mLength == 0) {
+ return false;
+ }
+
+ if (aDisplay->mOverflowX == NS_STYLE_OVERFLOW_CLIP) {
+ *aStyles = ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN,
+ NS_STYLE_OVERFLOW_HIDDEN, aDisplay);
+ } else {
+ *aStyles = ScrollbarStyles(aDisplay);
+ }
+ return true;
+}
+
+static nsIContent*
+GetPropagatedScrollbarStylesForViewport(nsPresContext* aPresContext,
+ ScrollbarStyles *aStyles)
+{
+ nsIDocument* document = aPresContext->Document();
+ Element* docElement = document->GetRootElement();
+
+ // docElement might be null if we're doing this after removing it.
+ if (!docElement) {
+ return nullptr;
+ }
+
+ // Check the style on the document root element
+ StyleSetHandle styleSet = aPresContext->StyleSet();
+ RefPtr<nsStyleContext> rootStyle;
+ rootStyle = styleSet->ResolveStyleFor(docElement, nullptr);
+ if (CheckOverflow(rootStyle->StyleDisplay(), aStyles)) {
+ // tell caller we stole the overflow style from the root element
+ return docElement;
+ }
+
+ // Don't look in the BODY for non-HTML documents or HTML documents
+ // with non-HTML roots
+ // XXX this should be earlier; we shouldn't even look at the document root
+ // for non-HTML documents. Fix this once we support explicit CSS styling
+ // of the viewport
+ // XXX what about XHTML?
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(document));
+ if (!htmlDoc || !docElement->IsHTMLElement()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMHTMLElement> body;
+ htmlDoc->GetBody(getter_AddRefs(body));
+ nsCOMPtr<nsIContent> bodyElement = do_QueryInterface(body);
+
+ if (!bodyElement ||
+ !bodyElement->NodeInfo()->Equals(nsGkAtoms::body)) {
+ // The body is not a <body> tag, it's a <frameset>.
+ return nullptr;
+ }
+
+ RefPtr<nsStyleContext> bodyStyle;
+ bodyStyle = styleSet->ResolveStyleFor(bodyElement->AsElement(), rootStyle);
+
+ if (CheckOverflow(bodyStyle->StyleDisplay(), aStyles)) {
+ // tell caller we stole the overflow style from the body element
+ return bodyElement;
+ }
+
+ return nullptr;
+}
+
+nsIContent*
+nsPresContext::UpdateViewportScrollbarStylesOverride()
+{
+ // Start off with our default styles, and then update them as needed.
+ mViewportStyleScrollbar = ScrollbarStyles(NS_STYLE_OVERFLOW_AUTO,
+ NS_STYLE_OVERFLOW_AUTO);
+ nsIContent* propagatedFrom = nullptr;
+ // Don't propagate the scrollbar state in printing or print preview.
+ if (!IsPaginated()) {
+ propagatedFrom =
+ GetPropagatedScrollbarStylesForViewport(this, &mViewportStyleScrollbar);
+ }
+
+ nsIDocument* document = Document();
+ if (Element* fullscreenElement = document->GetFullscreenElement()) {
+ // If the document is in fullscreen, but the fullscreen element is
+ // not the root element, we should explicitly suppress the scrollbar
+ // here. Note that, we still need to return the original element
+ // the styles are from, so that the state of those elements is not
+ // affected across fullscreen change.
+ if (fullscreenElement != document->GetRootElement() &&
+ fullscreenElement != propagatedFrom) {
+ mViewportStyleScrollbar = ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN,
+ NS_STYLE_OVERFLOW_HIDDEN);
+ }
+ }
+
+ return propagatedFrom;
+}
+
+bool
+nsPresContext::ElementWouldPropagateScrollbarStyles(Element* aElement)
+{
+ MOZ_ASSERT(IsPaginated(), "Should only be called on paginated contexts");
+ if (aElement->GetParent() && !aElement->IsHTMLElement(nsGkAtoms::body)) {
+ // We certainly won't be propagating from this element.
+ return false;
+ }
+
+ // Go ahead and just call GetPropagatedScrollbarStylesForViewport, but update
+ // a dummy ScrollbarStyles we don't care about. It'll do a bit of extra work,
+ // but saves us having to have more complicated code or more code duplication;
+ // in practice we will make this call quite rarely, because we checked for all
+ // the common cases above.
+ ScrollbarStyles dummy(NS_STYLE_OVERFLOW_AUTO, NS_STYLE_OVERFLOW_AUTO);
+ return GetPropagatedScrollbarStylesForViewport(this, &dummy) == aElement;
+}
+
+void
+nsPresContext::SetContainer(nsIDocShell* aDocShell)
+{
+ if (aDocShell) {
+ NS_ASSERTION(!(mContainer && mNeedsPrefUpdate),
+ "Should only need pref update if mContainer is null.");
+ mContainer = static_cast<nsDocShell*>(aDocShell);
+ if (mNeedsPrefUpdate) {
+ if (!mPrefChangedTimer) {
+ mPrefChangedTimer = CreateTimer(PrefChangedUpdateTimerCallback, 0);
+ }
+ mNeedsPrefUpdate = false;
+ }
+ } else {
+ mContainer = WeakPtr<nsDocShell>();
+ }
+ UpdateIsChrome();
+ if (mContainer) {
+ GetDocumentColorPreferences();
+ }
+}
+
+nsISupports*
+nsPresContext::GetContainerWeakInternal() const
+{
+ return static_cast<nsIDocShell*>(mContainer);
+}
+
+nsISupports*
+nsPresContext::GetContainerWeakExternal() const
+{
+ return GetContainerWeakInternal();
+}
+
+nsIDocShell*
+nsPresContext::GetDocShell() const
+{
+ return mContainer;
+}
+
+/* virtual */ void
+nsPresContext::Detach()
+{
+ SetContainer(nullptr);
+ SetLinkHandler(nullptr);
+ if (mShell) {
+ mShell->CancelInvalidatePresShellIfHidden();
+ }
+}
+
+bool
+nsPresContext::BidiEnabledExternal() const
+{
+ return BidiEnabledInternal();
+}
+
+bool
+nsPresContext::BidiEnabledInternal() const
+{
+ return Document()->GetBidiEnabled();
+}
+
+void
+nsPresContext::SetBidiEnabled() const
+{
+ if (mShell) {
+ nsIDocument *doc = mShell->GetDocument();
+ if (doc) {
+ doc->SetBidiEnabled();
+ }
+ }
+}
+
+void
+nsPresContext::SetBidi(uint32_t aSource, bool aForceRestyle)
+{
+ // Don't do all this stuff unless the options have changed.
+ if (aSource == GetBidi()) {
+ return;
+ }
+
+ NS_ASSERTION(!(aForceRestyle && (GetBidi() == 0)),
+ "ForceReflow on new prescontext");
+
+ Document()->SetBidiOptions(aSource);
+ if (IBMBIDI_TEXTDIRECTION_RTL == GET_BIDI_OPTION_DIRECTION(aSource)
+ || IBMBIDI_NUMERAL_HINDI == GET_BIDI_OPTION_NUMERAL(aSource)) {
+ SetBidiEnabled();
+ }
+ if (IBMBIDI_TEXTTYPE_VISUAL == GET_BIDI_OPTION_TEXTTYPE(aSource)) {
+ SetVisualMode(true);
+ }
+ else if (IBMBIDI_TEXTTYPE_LOGICAL == GET_BIDI_OPTION_TEXTTYPE(aSource)) {
+ SetVisualMode(false);
+ }
+ else {
+ nsIDocument* doc = mShell->GetDocument();
+ if (doc) {
+ SetVisualMode(IsVisualCharset(doc->GetDocumentCharacterSet()));
+ }
+ }
+ if (aForceRestyle && mShell) {
+ // Reconstruct the root document element's frame and its children,
+ // because we need to trigger frame reconstruction for direction change.
+ mDocument->RebuildUserFontSet();
+ mShell->ReconstructFrames();
+ }
+}
+
+uint32_t
+nsPresContext::GetBidi() const
+{
+ return Document()->GetBidiOptions();
+}
+
+bool
+nsPresContext::IsTopLevelWindowInactive()
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(mContainer);
+ if (!treeItem)
+ return false;
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootItem));
+ if (!rootItem) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow();
+
+ return domWindow && !domWindow->IsActive();
+}
+
+nsITheme*
+nsPresContext::GetTheme()
+{
+ if (!sNoTheme && !mTheme) {
+ mTheme = do_GetService("@mozilla.org/chrome/chrome-native-theme;1");
+ if (!mTheme)
+ sNoTheme = true;
+ }
+
+ return mTheme;
+}
+
+void
+nsPresContext::ThemeChanged()
+{
+ if (!mPendingThemeChanged) {
+ sLookAndFeelChanged = true;
+ sThemeChanged = true;
+
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsPresContext::ThemeChangedInternal);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPendingThemeChanged = true;
+ }
+ }
+}
+
+static bool
+NotifyThemeChanged(TabParent* aTabParent, void* aArg)
+{
+ aTabParent->ThemeChanged();
+ return false;
+}
+
+void
+nsPresContext::ThemeChangedInternal()
+{
+ mPendingThemeChanged = false;
+
+ // Tell the theme that it changed, so it can flush any handles to stale theme
+ // data.
+ if (mTheme && sThemeChanged) {
+ mTheme->ThemeChanged();
+ sThemeChanged = false;
+ }
+
+ if (sLookAndFeelChanged) {
+ // Clear all cached LookAndFeel colors.
+ LookAndFeel::Refresh();
+ sLookAndFeelChanged = false;
+
+ // Vector images (SVG) may be using theme colors so we discard all cached
+ // surfaces. (We could add a vector image only version of DiscardAll, but
+ // in bug 940625 we decided theme changes are rare enough not to bother.)
+ image::SurfaceCacheUtils::DiscardAll();
+ }
+
+ // This will force the system metrics to be generated the next time they're used
+ nsCSSRuleProcessor::FreeSystemMetrics();
+
+ // Changes to system metrics can change media queries on them, or
+ // :-moz-system-metric selectors (which requires eRestyle_Subtree).
+ // Changes in theme can change system colors (whose changes are
+ // properly reflected in computed style data), system fonts (whose
+ // changes are not), and -moz-appearance (whose changes likewise are
+ // not), so we need to reflow.
+ MediaFeatureValuesChanged(eRestyle_Subtree, NS_STYLE_HINT_REFLOW);
+
+ // Recursively notify all remote leaf descendants that the
+ // system theme has changed.
+ nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
+ NotifyThemeChanged, nullptr);
+}
+
+void
+nsPresContext::SysColorChanged()
+{
+ if (!mPendingSysColorChanged) {
+ sLookAndFeelChanged = true;
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsPresContext::SysColorChangedInternal);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPendingSysColorChanged = true;
+ }
+ }
+}
+
+void
+nsPresContext::SysColorChangedInternal()
+{
+ mPendingSysColorChanged = false;
+
+ if (sLookAndFeelChanged) {
+ // Don't use the cached values for the system colors
+ LookAndFeel::Refresh();
+ sLookAndFeelChanged = false;
+ }
+
+ // Reset default background and foreground colors for the document since
+ // they may be using system colors
+ GetDocumentColorPreferences();
+
+ // The system color values are computed to colors in the style data,
+ // so normal style data comparison is sufficient here.
+ RebuildAllStyleData(nsChangeHint(0), nsRestyleHint(0));
+}
+
+void
+nsPresContext::UIResolutionChanged()
+{
+ if (!mPendingUIResolutionChanged) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsPresContext::UIResolutionChangedInternal);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPendingUIResolutionChanged = true;
+ }
+ }
+}
+
+void
+nsPresContext::UIResolutionChangedSync()
+{
+ if (!mPendingUIResolutionChanged) {
+ mPendingUIResolutionChanged = true;
+ UIResolutionChangedInternalScale(0.0);
+ }
+}
+
+/*static*/ bool
+nsPresContext::UIResolutionChangedSubdocumentCallback(nsIDocument* aDocument,
+ void* aData)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* pc = shell->GetPresContext();
+ if (pc) {
+ // For subdocuments, we want to apply the parent's scale, because there
+ // are cases where the subdoc's device context is connected to a widget
+ // that has an out-of-date resolution (it's on a different screen, but
+ // currently hidden, and will not be updated until shown): bug 1249279.
+ double scale = *static_cast<double*>(aData);
+ pc->UIResolutionChangedInternalScale(scale);
+ }
+ }
+ return true;
+}
+
+static void
+NotifyTabUIResolutionChanged(TabParent* aTab, void *aArg)
+{
+ aTab->UIResolutionChanged();
+}
+
+static void
+NotifyChildrenUIResolutionChanged(nsPIDOMWindowOuter* aWindow)
+{
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ RefPtr<nsPIWindowRoot> topLevelWin = nsContentUtils::GetWindowRoot(doc);
+ if (!topLevelWin) {
+ return;
+ }
+ topLevelWin->EnumerateBrowsers(NotifyTabUIResolutionChanged, nullptr);
+}
+
+void
+nsPresContext::UIResolutionChangedInternal()
+{
+ UIResolutionChangedInternalScale(0.0);
+}
+
+void
+nsPresContext::UIResolutionChangedInternalScale(double aScale)
+{
+ mPendingUIResolutionChanged = false;
+
+ mDeviceContext->CheckDPIChange(&aScale);
+ if (mCurAppUnitsPerDevPixel != AppUnitsPerDevPixel()) {
+ AppUnitsPerDevPixelChanged();
+ }
+
+ // Recursively notify all remote leaf descendants of the change.
+ if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
+ NotifyChildrenUIResolutionChanged(window);
+ }
+
+ mDocument->EnumerateSubDocuments(UIResolutionChangedSubdocumentCallback,
+ &aScale);
+}
+
+void
+nsPresContext::EmulateMedium(const nsAString& aMediaType)
+{
+ nsIAtom* previousMedium = Medium();
+ mIsEmulatingMedia = true;
+
+ nsAutoString mediaType;
+ nsContentUtils::ASCIIToLower(aMediaType, mediaType);
+
+ mMediaEmulated = NS_Atomize(mediaType);
+ if (mMediaEmulated != previousMedium && mShell) {
+ MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+ }
+}
+
+void nsPresContext::StopEmulatingMedium()
+{
+ nsIAtom* previousMedium = Medium();
+ mIsEmulatingMedia = false;
+ if (Medium() != previousMedium) {
+ MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+ }
+}
+
+void
+nsPresContext::RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ if (!mShell) {
+ // We must have been torn down. Nothing to do here.
+ return;
+ }
+
+ mUsesRootEMUnits = false;
+ mUsesExChUnits = false;
+ mUsesViewportUnits = false;
+ mDocument->RebuildUserFontSet();
+ RebuildCounterStyles();
+
+ RestyleManager()->RebuildAllStyleData(aExtraHint, aRestyleHint);
+}
+
+void
+nsPresContext::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ if (!mShell) {
+ // We must have been torn down. Nothing to do here.
+ return;
+ }
+ RestyleManager()->PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint);
+}
+
+struct MediaFeatureHints
+{
+ nsRestyleHint restyleHint;
+ nsChangeHint changeHint;
+};
+
+static bool
+MediaFeatureValuesChangedAllDocumentsCallback(nsIDocument* aDocument, void* aHints)
+{
+ MediaFeatureHints* hints = static_cast<MediaFeatureHints*>(aHints);
+ if (nsIPresShell* shell = aDocument->GetShell()) {
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->MediaFeatureValuesChangedAllDocuments(hints->restyleHint,
+ hints->changeHint);
+ }
+ }
+ return true;
+}
+
+void
+nsPresContext::MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint)
+{
+ MediaFeatureValuesChanged(aRestyleHint, aChangeHint);
+ MediaFeatureHints hints = {
+ aRestyleHint,
+ aChangeHint
+ };
+
+ mDocument->EnumerateSubDocuments(MediaFeatureValuesChangedAllDocumentsCallback,
+ &hints);
+}
+
+void
+nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint)
+{
+ mPendingMediaFeatureValuesChanged = false;
+
+ // MediumFeaturesChanged updates the applied rules, so it always gets called.
+ if (mShell) {
+ // XXXheycam ServoStyleSets don't support responding to medium
+ // changes yet.
+ if (mShell->StyleSet()->IsGecko()) {
+ if (mShell->StyleSet()->AsGecko()->MediumFeaturesChanged()) {
+ aRestyleHint |= eRestyle_Subtree;
+ }
+ } else {
+ NS_WARNING("stylo: ServoStyleSets don't support responding to medium "
+ "changes yet. See bug 1290228.");
+ }
+ }
+
+ if (mUsesViewportUnits && mPendingViewportChange) {
+ // Rebuild all style data without rerunning selector matching.
+ aRestyleHint |= eRestyle_ForceDescendants;
+ }
+
+ if (aRestyleHint || aChangeHint) {
+ RebuildAllStyleData(aChangeHint, aRestyleHint);
+ }
+
+ mPendingViewportChange = false;
+
+ if (mDocument->IsBeingUsedAsImage()) {
+ MOZ_ASSERT(PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists()));
+ return;
+ }
+
+ mDocument->NotifyMediaFeatureValuesChanged();
+
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ // Media query list listeners should be notified from a queued task
+ // (in HTML5 terms), although we also want to notify them on certain
+ // flushes. (We're already running off an event.)
+ //
+ // Note that we do this after the new style from media queries in
+ // style sheets has been computed.
+
+ if (!PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists())) {
+ // We build a list of all the notifications we're going to send
+ // before we send any of them. (The spec says the notifications
+ // should be a queued task, so any removals that happen during the
+ // notifications shouldn't affect what gets notified.) Furthermore,
+ // we hold strong pointers to everything we're going to make
+ // notification calls to, since each notification involves calling
+ // arbitrary script that might otherwise destroy these objects, or,
+ // for that matter, |this|.
+ //
+ // Note that we intentionally send the notifications to media query
+ // list in the order they were created and, for each list, to the
+ // listeners in the order added.
+ nsTArray<MediaQueryList::HandleChangeData> notifyList;
+ for (PRCList *l = PR_LIST_HEAD(mDocument->MediaQueryLists());
+ l != mDocument->MediaQueryLists(); l = PR_NEXT_LINK(l)) {
+ MediaQueryList *mql = static_cast<MediaQueryList*>(l);
+ mql->MediumFeaturesChanged(notifyList);
+ }
+
+ if (!notifyList.IsEmpty()) {
+ for (uint32_t i = 0, i_end = notifyList.Length(); i != i_end; ++i) {
+ nsAutoMicroTask mt;
+ MediaQueryList::HandleChangeData &d = notifyList[i];
+ d.callback->Call(*d.mql);
+ }
+ }
+
+ // NOTE: When |notifyList| goes out of scope, our destructor could run.
+ }
+}
+
+void
+nsPresContext::PostMediaFeatureValuesChangedEvent()
+{
+ // FIXME: We should probably replace this event with use of
+ // nsRefreshDriver::AddStyleFlushObserver (except the pres shell would
+ // need to track whether it's been added).
+ if (!mPendingMediaFeatureValuesChanged) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsPresContext::HandleMediaFeatureValuesChangedEvent);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPendingMediaFeatureValuesChanged = true;
+ mDocument->SetNeedStyleFlush();
+ }
+ }
+}
+
+void
+nsPresContext::HandleMediaFeatureValuesChangedEvent()
+{
+ // Null-check mShell in case the shell has been destroyed (and the
+ // event is the only thing holding the pres context alive).
+ if (mPendingMediaFeatureValuesChanged && mShell) {
+ MediaFeatureValuesChanged(nsRestyleHint(0));
+ }
+}
+
+static bool
+NotifyTabSizeModeChanged(TabParent* aTab, void* aArg)
+{
+ nsSizeMode* sizeMode = static_cast<nsSizeMode*>(aArg);
+ aTab->SizeModeChanged(*sizeMode);
+ return false;
+}
+
+void
+nsPresContext::SizeModeChanged(nsSizeMode aSizeMode)
+{
+ if (HasCachedStyleData()) {
+ nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
+ NotifyTabSizeModeChanged,
+ &aSizeMode);
+ MediaFeatureValuesChangedAllDocuments(nsRestyleHint(0));
+ }
+}
+
+nsCompatibility
+nsPresContext::CompatibilityMode() const
+{
+ return Document()->GetCompatibilityMode();
+}
+
+void
+nsPresContext::SetPaginatedScrolling(bool aPaginated)
+{
+ if (mType == eContext_PrintPreview || mType == eContext_PageLayout)
+ mCanPaginatedScroll = aPaginated;
+}
+
+void
+nsPresContext::SetPrintSettings(nsIPrintSettings *aPrintSettings)
+{
+ if (mMedium == nsGkAtoms::print)
+ mPrintSettings = aPrintSettings;
+}
+
+bool
+nsPresContext::EnsureVisible()
+{
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell) {
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ // Make sure this is the content viewer we belong with
+ if (cv) {
+ RefPtr<nsPresContext> currentPresContext;
+ cv->GetPresContext(getter_AddRefs(currentPresContext));
+ if (currentPresContext == this) {
+ // OK, this is us. We want to call Show() on the content viewer.
+ nsresult result = cv->Show();
+ if (NS_SUCCEEDED(result)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+#ifdef MOZ_REFLOW_PERF
+void
+nsPresContext::CountReflows(const char * aName, nsIFrame * aFrame)
+{
+ if (mShell) {
+ mShell->CountReflows(aName, aFrame);
+ }
+}
+#endif
+
+void
+nsPresContext::UpdateIsChrome()
+{
+ mIsChrome = mContainer &&
+ nsIDocShellTreeItem::typeChrome == mContainer->ItemType();
+}
+
+bool
+nsPresContext::HasAuthorSpecifiedRules(const nsIFrame *aFrame,
+ uint32_t ruleTypeMask) const
+{
+ return
+ nsRuleNode::HasAuthorSpecifiedRules(aFrame->StyleContext(),
+ ruleTypeMask,
+ UseDocumentColors());
+}
+
+gfxUserFontSet*
+nsPresContext::GetUserFontSet(bool aFlushUserFontSet)
+{
+ return mDocument->GetUserFontSet(aFlushUserFontSet);
+}
+
+void
+nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont)
+{
+ if (!mShell)
+ return;
+
+ bool usePlatformFontList = true;
+#if defined(MOZ_WIDGET_GTK)
+ usePlatformFontList = gfxPlatformGtk::UseFcFontList();
+#endif
+
+ // xxx - until the Linux platform font list is always used, use full
+ // restyle to force updates with gfxPangoFontGroup usage
+ // Note: this method is called without a font when rules in the userfont set
+ // are updated, which may occur during reflow as a result of the lazy
+ // initialization of the userfont set. It would be better to avoid a full
+ // restyle but until this method is only called outside of reflow, schedule a
+ // full restyle in these cases.
+ if (!usePlatformFontList || !aUpdatedFont) {
+ PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+ return;
+ }
+
+ // Special case - if either the 'ex' or 'ch' units are used, these
+ // depend upon font metrics. Updating this information requires
+ // rebuilding the rule tree from the top, avoiding the reuse of cached
+ // data even when no style rules have changed.
+ if (UsesExChUnits()) {
+ PostRebuildAllStyleDataEvent(nsChangeHint(0), eRestyle_ForceDescendants);
+ }
+
+ // Iterate over the frame tree looking for frames associated with the
+ // downloadable font family in question. If a frame's nsStyleFont has
+ // the name, check the font group associated with the metrics to see if
+ // it contains that specific font (i.e. the one chosen within the family
+ // given the weight, width, and slant from the nsStyleFont). If it does,
+ // mark that frame dirty and skip inspecting its descendants.
+ nsIFrame* root = mShell->GetRootFrame();
+ if (root) {
+ nsFontFaceUtils::MarkDirtyForFontChange(root, aUpdatedFont);
+ }
+}
+
+void
+nsPresContext::FlushCounterStyles()
+{
+ if (!mShell) {
+ return; // we've been torn down
+ }
+ if (mCounterStyleManager->IsInitial()) {
+ // Still in its initial state, no need to clean.
+ return;
+ }
+
+ if (mCounterStylesDirty) {
+ bool changed = mCounterStyleManager->NotifyRuleChanged();
+ if (changed) {
+ PresShell()->NotifyCounterStylesAreDirty();
+ PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW,
+ eRestyle_ForceDescendants);
+ }
+ mCounterStylesDirty = false;
+ }
+}
+
+void
+nsPresContext::RebuildCounterStyles()
+{
+ if (mCounterStyleManager->IsInitial()) {
+ // Still in its initial state, no need to reset.
+ return;
+ }
+
+ mCounterStylesDirty = true;
+ mDocument->SetNeedStyleFlush();
+ if (!mPostedFlushCounterStyles) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsPresContext::HandleRebuildCounterStyles);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPostedFlushCounterStyles = true;
+ }
+ }
+}
+
+void
+nsPresContext::NotifyMissingFonts()
+{
+ if (mMissingFonts) {
+ mMissingFonts->Flush();
+ }
+}
+
+void
+nsPresContext::EnsureSafeToHandOutCSSRules()
+{
+ nsStyleSet* styleSet = mShell->StyleSet()->GetAsGecko();
+ if (!styleSet) {
+ // ServoStyleSets do not need to handle copy-on-write style sheet
+ // innards like with CSSStyleSheets.
+ return;
+ }
+
+ if (!styleSet->EnsureUniqueInnerOnCSSSheets()) {
+ // Nothing to do.
+ return;
+ }
+
+ RebuildAllStyleData(nsChangeHint(0), eRestyle_Subtree);
+}
+
+void
+nsPresContext::FireDOMPaintEvent(nsInvalidateRequestList* aList, uint64_t aTransactionId)
+{
+ nsPIDOMWindowInner* ourWindow = mDocument->GetInnerWindow();
+ if (!ourWindow)
+ return;
+
+ nsCOMPtr<EventTarget> dispatchTarget = do_QueryInterface(ourWindow);
+ nsCOMPtr<EventTarget> eventTarget = dispatchTarget;
+ if (!IsChrome() && !mSendAfterPaintToContent) {
+ // Don't tell the window about this event, it should not know that
+ // something happened in a subdocument. Tell only the chrome event handler.
+ // (Events sent to the window get propagated to the chrome event handler
+ // automatically.)
+ dispatchTarget = do_QueryInterface(ourWindow->GetParentTarget());
+ if (!dispatchTarget) {
+ return;
+ }
+ }
+ // Events sent to the window get propagated to the chrome event handler
+ // automatically.
+ //
+ // This will empty our list in case dispatching the event causes more damage
+ // (hopefully it won't, or we're likely to get an infinite loop! At least
+ // it won't be blocking app execution though).
+ RefPtr<NotifyPaintEvent> event =
+ NS_NewDOMNotifyPaintEvent(eventTarget, this, nullptr, eAfterPaint, aList, aTransactionId);
+
+ // Even if we're not telling the window about the event (so eventTarget is
+ // the chrome event handler, not the window), the window is still
+ // logically the event target.
+ event->SetTarget(eventTarget);
+ event->SetTrusted(true);
+ EventDispatcher::DispatchDOMEvent(dispatchTarget, nullptr,
+ static_cast<Event*>(event), this, nullptr);
+}
+
+static bool
+MayHavePaintEventListenerSubdocumentCallback(nsIDocument* aDocument, void* aData)
+{
+ bool *result = static_cast<bool*>(aData);
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* pc = shell->GetPresContext();
+ if (pc) {
+ *result = pc->MayHavePaintEventListenerInSubDocument();
+
+ // If we found a paint event listener, then we can stop enumerating
+ // sub documents.
+ return !*result;
+ }
+ }
+ return true;
+}
+
+static bool
+MayHavePaintEventListener(nsPIDOMWindowInner* aInnerWindow)
+{
+ if (!aInnerWindow)
+ return false;
+ if (aInnerWindow->HasPaintEventListeners())
+ return true;
+
+ EventTarget* parentTarget = aInnerWindow->GetParentTarget();
+ if (!parentTarget)
+ return false;
+
+ EventListenerManager* manager = nullptr;
+ if ((manager = parentTarget->GetExistingListenerManager()) &&
+ manager->MayHavePaintEventListener()) {
+ return true;
+ }
+
+ nsCOMPtr<nsINode> node;
+ if (parentTarget != aInnerWindow->GetChromeEventHandler()) {
+ nsCOMPtr<nsIInProcessContentFrameMessageManager> mm =
+ do_QueryInterface(parentTarget);
+ if (mm) {
+ node = mm->GetOwnerContent();
+ }
+ }
+
+ if (!node) {
+ node = do_QueryInterface(parentTarget);
+ }
+ if (node)
+ return MayHavePaintEventListener(node->OwnerDoc()->GetInnerWindow());
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentTarget);
+ if (window)
+ return MayHavePaintEventListener(window);
+
+ nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(parentTarget);
+ EventTarget* tabChildGlobal;
+ return root &&
+ (tabChildGlobal = root->GetParentTarget()) &&
+ (manager = tabChildGlobal->GetExistingListenerManager()) &&
+ manager->MayHavePaintEventListener();
+}
+
+bool
+nsPresContext::MayHavePaintEventListener()
+{
+ return ::MayHavePaintEventListener(mDocument->GetInnerWindow());
+}
+
+bool
+nsPresContext::MayHavePaintEventListenerInSubDocument()
+{
+ if (MayHavePaintEventListener()) {
+ return true;
+ }
+
+ bool result = false;
+ mDocument->EnumerateSubDocuments(MayHavePaintEventListenerSubdocumentCallback, &result);
+ return result;
+}
+
+void
+nsPresContext::NotifyInvalidation(uint32_t aFlags)
+{
+ nsIFrame* rootFrame = PresShell()->FrameManager()->GetRootFrame();
+ NotifyInvalidation(rootFrame->GetVisualOverflowRect(), aFlags);
+ mAllInvalidated = true;
+}
+
+void
+nsPresContext::NotifyInvalidation(const nsIntRect& aRect, uint32_t aFlags)
+{
+ // Prevent values from overflow after DevPixelsToAppUnits().
+ //
+ // DevPixelsTopAppUnits() will multiple a factor (60) to the value,
+ // it may make the result value over the edge (overflow) of max or
+ // min value of int32_t. Compute the max sized dev pixel rect that
+ // we can support and intersect with it.
+ nsIntRect clampedRect = nsIntRect::MaxIntRect();
+ clampedRect.ScaleInverseRoundIn(AppUnitsPerDevPixel());
+
+ clampedRect = clampedRect.Intersect(aRect);
+
+ nsRect rect(DevPixelsToAppUnits(clampedRect.x),
+ DevPixelsToAppUnits(clampedRect.y),
+ DevPixelsToAppUnits(clampedRect.width),
+ DevPixelsToAppUnits(clampedRect.height));
+ NotifyInvalidation(rect, aFlags);
+}
+
+void
+nsPresContext::NotifyInvalidation(const nsRect& aRect, uint32_t aFlags)
+{
+ MOZ_ASSERT(GetContainerWeak(), "Invalidation in detached pres context");
+
+ // If there is no paint event listener, then we don't need to fire
+ // the asynchronous event. We don't even need to record invalidation.
+ // MayHavePaintEventListener is pretty cheap and we could make it
+ // even cheaper by providing a more efficient
+ // nsPIDOMWindow::GetListenerManager.
+
+ if (mAllInvalidated) {
+ return;
+ }
+
+ nsPresContext* pc;
+ for (pc = this; pc; pc = pc->GetParentPresContext()) {
+ if (pc->mFireAfterPaintEvents)
+ break;
+ pc->mFireAfterPaintEvents = true;
+ }
+ if (!pc) {
+ nsRootPresContext* rpc = GetRootPresContext();
+ if (rpc) {
+ rpc->EnsureEventualDidPaintEvent();
+ }
+ }
+
+ nsInvalidateRequestList::Request* request =
+ mInvalidateRequestsSinceLastPaint.mRequests.AppendElement();
+ if (!request)
+ return;
+
+ request->mRect = aRect;
+ request->mFlags = aFlags;
+}
+
+/* static */ void
+nsPresContext::NotifySubDocInvalidation(ContainerLayer* aContainer,
+ const nsIntRegion& aRegion)
+{
+ ContainerLayerPresContext *data =
+ static_cast<ContainerLayerPresContext*>(
+ aContainer->GetUserData(&gNotifySubDocInvalidationData));
+ if (!data) {
+ return;
+ }
+
+ nsIntPoint topLeft = aContainer->GetVisibleRegion().ToUnknownRegion().GetBounds().TopLeft();
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ nsIntRect rect(iter.Get());
+ //PresContext coordinate space is relative to the start of our visible
+ // region. Is this really true? This feels like the wrong way to get the right
+ // answer.
+ rect.MoveBy(-topLeft);
+ data->mPresContext->NotifyInvalidation(rect, 0);
+ }
+}
+
+void
+nsPresContext::SetNotifySubDocInvalidationData(ContainerLayer* aContainer)
+{
+ ContainerLayerPresContext* pres = new ContainerLayerPresContext;
+ pres->mPresContext = this;
+ aContainer->SetUserData(&gNotifySubDocInvalidationData, pres);
+}
+
+/* static */ void
+nsPresContext::ClearNotifySubDocInvalidationData(ContainerLayer* aContainer)
+{
+ aContainer->SetUserData(&gNotifySubDocInvalidationData, nullptr);
+}
+
+struct NotifyDidPaintSubdocumentCallbackClosure {
+ uint32_t mFlags;
+ uint64_t mTransactionId;
+ bool mNeedsAnotherDidPaintNotification;
+};
+static bool
+NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
+{
+ NotifyDidPaintSubdocumentCallbackClosure* closure =
+ static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData);
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ nsPresContext* pc = shell->GetPresContext();
+ if (pc) {
+ pc->NotifyDidPaintForSubtree(closure->mFlags, closure->mTransactionId);
+ if (pc->IsDOMPaintEventPending()) {
+ closure->mNeedsAnotherDidPaintNotification = true;
+ }
+ }
+ }
+ return true;
+}
+
+class DelayedFireDOMPaintEvent : public Runnable {
+public:
+ DelayedFireDOMPaintEvent(nsPresContext* aPresContext,
+ nsInvalidateRequestList* aList,
+ uint64_t aTransactionId)
+ : mPresContext(aPresContext)
+ , mTransactionId(aTransactionId)
+ {
+ MOZ_ASSERT(mPresContext->GetContainerWeak(),
+ "DOMPaintEvent requested for a detached pres context");
+ mList.TakeFrom(aList);
+ }
+ NS_IMETHOD Run() override
+ {
+ // The pres context might have been detached during the delay -
+ // that's fine, just don't fire the event.
+ if (mPresContext->GetContainerWeak()) {
+ mPresContext->FireDOMPaintEvent(&mList, mTransactionId);
+ }
+ return NS_OK;
+ }
+
+ RefPtr<nsPresContext> mPresContext;
+ uint64_t mTransactionId;
+ nsInvalidateRequestList mList;
+};
+
+void
+nsPresContext::NotifyDidPaintForSubtree(uint32_t aFlags, uint64_t aTransactionId,
+ const mozilla::TimeStamp& aTimeStamp)
+{
+ if (IsRoot()) {
+ static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
+
+ if (!mFireAfterPaintEvents) {
+ return;
+ }
+ }
+
+ if (!PresShell()->IsVisible() && !mFireAfterPaintEvents) {
+ return;
+ }
+
+ // Non-root prescontexts fire MozAfterPaint to all their descendants
+ // unconditionally, even if no invalidations have been collected. This is
+ // because we don't want to eat the cost of collecting invalidations for
+ // every subdocument (which would require putting every subdocument in its
+ // own layer).
+
+ if (aFlags & nsIPresShell::PAINT_LAYERS) {
+ mUndeliveredInvalidateRequestsBeforeLastPaint.TakeFrom(
+ &mInvalidateRequestsSinceLastPaint);
+ mAllInvalidated = false;
+ }
+ if (aFlags & nsIPresShell::PAINT_COMPOSITE) {
+ nsCOMPtr<nsIRunnable> ev =
+ new DelayedFireDOMPaintEvent(this, &mUndeliveredInvalidateRequestsBeforeLastPaint,
+ aTransactionId);
+ nsContentUtils::AddScriptRunner(ev);
+ }
+
+ NotifyDidPaintSubdocumentCallbackClosure closure = { aFlags, aTransactionId, false };
+ mDocument->EnumerateSubDocuments(NotifyDidPaintSubdocumentCallback, &closure);
+
+ if (!closure.mNeedsAnotherDidPaintNotification &&
+ mInvalidateRequestsSinceLastPaint.IsEmpty() &&
+ mUndeliveredInvalidateRequestsBeforeLastPaint.IsEmpty()) {
+ // Nothing more to do for the moment.
+ mFireAfterPaintEvents = false;
+ } else {
+ if (IsRoot()) {
+ static_cast<nsRootPresContext*>(this)->EnsureEventualDidPaintEvent();
+ }
+ }
+}
+
+bool
+nsPresContext::HasCachedStyleData()
+{
+ if (!mShell) {
+ return false;
+ }
+
+ nsStyleSet* styleSet = mShell->StyleSet()->GetAsGecko();
+ if (!styleSet) {
+ // XXXheycam ServoStyleSets do not use the rule tree, so just assume for now
+ // that we need to restyle when e.g. dppx changes assuming we're sufficiently
+ // bootstrapped.
+ return mShell->DidInitialize();
+ }
+
+ return styleSet->HasCachedStyleData();
+}
+
+already_AddRefed<nsITimer>
+nsPresContext::CreateTimer(nsTimerCallbackFunc aCallback,
+ uint32_t aDelay)
+{
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (timer) {
+ nsresult rv = timer->InitWithFuncCallback(aCallback, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ return timer.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+static bool sGotInterruptEnv = false;
+enum InterruptMode {
+ ModeRandom,
+ ModeCounter,
+ ModeEvent
+};
+// Controlled by the GECKO_REFLOW_INTERRUPT_MODE env var; allowed values are
+// "random" (except on Windows) or "counter". If neither is used, the mode is
+// ModeEvent.
+static InterruptMode sInterruptMode = ModeEvent;
+#ifndef XP_WIN
+// Used for the "random" mode. Controlled by the GECKO_REFLOW_INTERRUPT_SEED
+// env var.
+static uint32_t sInterruptSeed = 1;
+#endif
+// Used for the "counter" mode. This is the number of unskipped interrupt
+// checks that have to happen before we interrupt. Controlled by the
+// GECKO_REFLOW_INTERRUPT_FREQUENCY env var.
+static uint32_t sInterruptMaxCounter = 10;
+// Used for the "counter" mode. This counts up to sInterruptMaxCounter and is
+// then reset to 0.
+static uint32_t sInterruptCounter;
+// Number of interrupt checks to skip before really trying to interrupt.
+// Controlled by the GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP env var.
+static uint32_t sInterruptChecksToSkip = 200;
+// Number of milliseconds that a reflow should be allowed to run for before we
+// actually allow interruption. Controlled by the
+// GECKO_REFLOW_MIN_NOINTERRUPT_DURATION env var. Can't be initialized here,
+// because TimeDuration/TimeStamp is not safe to use in static constructors..
+static TimeDuration sInterruptTimeout;
+
+static void GetInterruptEnv()
+{
+ char *ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_MODE");
+ if (ev) {
+#ifndef XP_WIN
+ if (PL_strcasecmp(ev, "random") == 0) {
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_SEED");
+ if (ev) {
+ sInterruptSeed = atoi(ev);
+ }
+ srandom(sInterruptSeed);
+ sInterruptMode = ModeRandom;
+ } else
+#endif
+ if (PL_strcasecmp(ev, "counter") == 0) {
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_FREQUENCY");
+ if (ev) {
+ sInterruptMaxCounter = atoi(ev);
+ }
+ sInterruptCounter = 0;
+ sInterruptMode = ModeCounter;
+ }
+ }
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP");
+ if (ev) {
+ sInterruptChecksToSkip = atoi(ev);
+ }
+
+ ev = PR_GetEnv("GECKO_REFLOW_MIN_NOINTERRUPT_DURATION");
+ int duration_ms = ev ? atoi(ev) : 100;
+ sInterruptTimeout = TimeDuration::FromMilliseconds(duration_ms);
+}
+
+bool
+nsPresContext::HavePendingInputEvent()
+{
+ switch (sInterruptMode) {
+#ifndef XP_WIN
+ case ModeRandom:
+ return (random() & 1);
+#endif
+ case ModeCounter:
+ if (sInterruptCounter < sInterruptMaxCounter) {
+ ++sInterruptCounter;
+ return false;
+ }
+ sInterruptCounter = 0;
+ return true;
+ default:
+ case ModeEvent: {
+ nsIFrame* f = PresShell()->GetRootFrame();
+ if (f) {
+ nsIWidget* w = f->GetNearestWidget();
+ if (w) {
+ return w->HasPendingInputEvent();
+ }
+ }
+ return false;
+ }
+ }
+}
+
+void
+nsPresContext::NotifyFontFaceSetOnRefresh()
+{
+ FontFaceSet* set = mDocument->GetFonts();
+ if (set) {
+ set->DidRefresh();
+ }
+}
+
+bool
+nsPresContext::HasPendingRestyleOrReflow()
+{
+ return (mRestyleManager && mRestyleManager->HasPendingRestyles()) ||
+ PresShell()->HasPendingReflow();
+}
+
+void
+nsPresContext::ReflowStarted(bool aInterruptible)
+{
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ if (!aInterruptible) {
+ printf("STARTING NONINTERRUPTIBLE REFLOW\n");
+ }
+#endif
+ // We don't support interrupting in paginated contexts, since page
+ // sequences only handle initial reflow
+ mInterruptsEnabled = aInterruptible && !IsPaginated() &&
+ nsLayoutUtils::InterruptibleReflowEnabled();
+
+ // Don't set mHasPendingInterrupt based on HavePendingInputEvent() here. If
+ // we ever change that, then we need to update the code in
+ // PresShell::DoReflow to only add the just-reflown root to dirty roots if
+ // it's actually dirty. Otherwise we can end up adding a root that has no
+ // interruptible descendants, just because we detected an interrupt at reflow
+ // start.
+ mHasPendingInterrupt = false;
+
+ mInterruptChecksToSkip = sInterruptChecksToSkip;
+
+ if (mInterruptsEnabled) {
+ mReflowStartTime = TimeStamp::Now();
+ }
+}
+
+bool
+nsPresContext::CheckForInterrupt(nsIFrame* aFrame)
+{
+ if (mHasPendingInterrupt) {
+ mShell->FrameNeedsToContinueReflow(aFrame);
+ return true;
+ }
+
+ if (!sGotInterruptEnv) {
+ sGotInterruptEnv = true;
+ GetInterruptEnv();
+ }
+
+ if (!mInterruptsEnabled) {
+ return false;
+ }
+
+ if (mInterruptChecksToSkip > 0) {
+ --mInterruptChecksToSkip;
+ return false;
+ }
+ mInterruptChecksToSkip = sInterruptChecksToSkip;
+
+ // Don't interrupt if it's been less than sInterruptTimeout since we started
+ // the reflow.
+ mHasPendingInterrupt =
+ TimeStamp::Now() - mReflowStartTime > sInterruptTimeout &&
+ HavePendingInputEvent() &&
+ !IsChrome();
+
+ if (mPendingInterruptFromTest) {
+ mPendingInterruptFromTest = false;
+ mHasPendingInterrupt = true;
+ }
+
+ if (mHasPendingInterrupt) {
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ printf("*** DETECTED pending interrupt (time=%lld)\n", PR_Now());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+ mShell->FrameNeedsToContinueReflow(aFrame);
+ }
+ return mHasPendingInterrupt;
+}
+
+nsIFrame*
+nsPresContext::GetPrimaryFrameFor(nsIContent* aContent)
+{
+ NS_PRECONDITION(aContent, "Don't do that");
+ if (GetPresShell() &&
+ GetPresShell()->GetDocument() == aContent->GetComposedDoc()) {
+ return aContent->GetPrimaryFrame();
+ }
+ return nullptr;
+}
+
+size_t
+nsPresContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mPropertyTable.SizeOfExcludingThis(aMallocSizeOf) +
+ mLangGroupFontPrefs.SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of other members may be added later if DMD finds it is
+ // worthwhile.
+}
+
+bool
+nsPresContext::IsRootContentDocument() const
+{
+ // We are a root content document if: we are not a resource doc, we are
+ // not chrome, and we either have no parent or our parent is chrome.
+ if (mDocument->IsResourceDoc()) {
+ return false;
+ }
+ if (IsChrome()) {
+ return false;
+ }
+ // We may not have a root frame, so use views.
+ nsView* view = PresShell()->GetViewManager()->GetRootView();
+ if (!view) {
+ return false;
+ }
+ view = view->GetParent(); // anonymous inner view
+ if (!view) {
+ return true;
+ }
+ view = view->GetParent(); // subdocumentframe's view
+ if (!view) {
+ return true;
+ }
+
+ nsIFrame* f = view->GetFrame();
+ return (f && f->PresContext()->IsChrome());
+}
+
+void
+nsPresContext::NotifyNonBlankPaint()
+{
+ MOZ_ASSERT(!mHadNonBlankPaint);
+ mHadNonBlankPaint = true;
+ if (IsRootContentDocument()) {
+ RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+ if (timing) {
+ timing->NotifyNonBlankPaintForRootContentDocument();
+ }
+ }
+}
+
+bool nsPresContext::GetPaintFlashing() const
+{
+ if (!mPaintFlashingInitialized) {
+ bool pref = Preferences::GetBool("nglayout.debug.paint_flashing");
+ if (!pref && IsChrome()) {
+ pref = Preferences::GetBool("nglayout.debug.paint_flashing_chrome");
+ }
+ mPaintFlashing = pref;
+ mPaintFlashingInitialized = true;
+ }
+ return mPaintFlashing;
+}
+
+int32_t
+nsPresContext::AppUnitsPerDevPixel() const
+{
+ return mDeviceContext->AppUnitsPerDevPixel();
+}
+
+nscoord
+nsPresContext::GfxUnitsToAppUnits(gfxFloat aGfxUnits) const
+{
+ return mDeviceContext->GfxUnitsToAppUnits(aGfxUnits);
+}
+
+gfxFloat
+nsPresContext::AppUnitsToGfxUnits(nscoord aAppUnits) const
+{
+ return mDeviceContext->AppUnitsToGfxUnits(aAppUnits);
+}
+
+bool
+nsPresContext::IsDeviceSizePageSize()
+{
+ bool isDeviceSizePageSize = false;
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell) {
+ isDeviceSizePageSize = docShell->GetDeviceSizeIsPageSize();
+ }
+ return isDeviceSizePageSize;
+}
+
+uint64_t
+nsPresContext::GetRestyleGeneration() const
+{
+ if (!mRestyleManager) {
+ return 0;
+ }
+ return mRestyleManager->GetRestyleGeneration();
+}
+
+nsRootPresContext::nsRootPresContext(nsIDocument* aDocument,
+ nsPresContextType aType)
+ : nsPresContext(aDocument, aType),
+ mDOMGeneration(0)
+{
+}
+
+nsRootPresContext::~nsRootPresContext()
+{
+ NS_ASSERTION(mRegisteredPlugins.Count() == 0,
+ "All plugins should have been unregistered");
+ CancelDidPaintTimer();
+ CancelApplyPluginGeometryTimer();
+}
+
+/* virtual */ void
+nsRootPresContext::Detach()
+{
+ CancelDidPaintTimer();
+ // XXXmats maybe also CancelApplyPluginGeometryTimer(); ?
+ nsPresContext::Detach();
+}
+
+void
+nsRootPresContext::RegisterPluginForGeometryUpdates(nsIContent* aPlugin)
+{
+ mRegisteredPlugins.PutEntry(aPlugin);
+}
+
+void
+nsRootPresContext::UnregisterPluginForGeometryUpdates(nsIContent* aPlugin)
+{
+ mRegisteredPlugins.RemoveEntry(aPlugin);
+}
+
+void
+nsRootPresContext::ComputePluginGeometryUpdates(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList)
+{
+ if (mRegisteredPlugins.Count() == 0) {
+ return;
+ }
+
+ // Initially make the next state for each plugin descendant of aFrame be
+ // "hidden". Plugins that are visible will have their next state set to
+ // unhidden by nsDisplayPlugin::ComputeVisibility.
+ for (auto iter = mRegisteredPlugins.Iter(); !iter.Done(); iter.Next()) {
+ auto f = static_cast<nsPluginFrame*>(iter.Get()->GetKey()->GetPrimaryFrame());
+ if (!f) {
+ NS_WARNING("Null frame in ComputePluginGeometryUpdates");
+ continue;
+ }
+ if (!nsLayoutUtils::IsAncestorFrameCrossDoc(aFrame, f)) {
+ // f is not managed by this frame so we should ignore it.
+ continue;
+ }
+ f->SetEmptyWidgetConfiguration();
+ }
+
+ nsIFrame* rootFrame = FrameManager()->GetRootFrame();
+
+ if (rootFrame && aBuilder->ContainsPluginItem()) {
+ aBuilder->SetForPluginGeometry();
+ aBuilder->SetAccurateVisibleRegions();
+ // Merging and flattening has already been done and we should not do it
+ // again. nsDisplayScroll(Info)Layer doesn't support trying to flatten
+ // again.
+ aBuilder->SetAllowMergingAndFlattening(false);
+ nsRegion region = rootFrame->GetVisualOverflowRectRelativeToSelf();
+ // nsDisplayPlugin::ComputeVisibility will automatically set a non-hidden
+ // widget configuration for the plugin, if it's visible.
+ aList->ComputeVisibilityForRoot(aBuilder, &region);
+ }
+
+#ifdef XP_MACOSX
+ // We control painting of Mac plugins, so just apply geometry updates now.
+ // This is not happening during a paint event.
+ ApplyPluginGeometryUpdates();
+#else
+ if (XRE_IsParentProcess()) {
+ InitApplyPluginGeometryTimer();
+ }
+#endif
+}
+
+static void
+ApplyPluginGeometryUpdatesCallback(nsITimer *aTimer, void *aClosure)
+{
+ static_cast<nsRootPresContext*>(aClosure)->ApplyPluginGeometryUpdates();
+}
+
+void
+nsRootPresContext::InitApplyPluginGeometryTimer()
+{
+ if (mApplyPluginGeometryTimer) {
+ return;
+ }
+
+ // We'll apply the plugin geometry updates during the next compositing paint in this
+ // presContext (either from nsPresShell::WillPaintWindow or from
+ // nsPresShell::DidPaintWindow, depending on the platform). But paints might
+ // get optimized away if the old plugin geometry covers the invalid region,
+ // so set a backup timer to do this too. We want to make sure this
+ // won't fire before our normal paint notifications, if those would
+ // update the geometry, so set it for double the refresh driver interval.
+ mApplyPluginGeometryTimer = CreateTimer(ApplyPluginGeometryUpdatesCallback,
+ nsRefreshDriver::DefaultInterval() * 2);
+}
+
+void
+nsRootPresContext::CancelApplyPluginGeometryTimer()
+{
+ if (mApplyPluginGeometryTimer) {
+ mApplyPluginGeometryTimer->Cancel();
+ mApplyPluginGeometryTimer = nullptr;
+ }
+}
+
+#ifndef XP_MACOSX
+
+static bool
+HasOverlap(const LayoutDeviceIntPoint& aOffset1,
+ const nsTArray<LayoutDeviceIntRect>& aClipRects1,
+ const LayoutDeviceIntPoint& aOffset2,
+ const nsTArray<LayoutDeviceIntRect>& aClipRects2)
+{
+ LayoutDeviceIntPoint offsetDelta = aOffset1 - aOffset2;
+ for (uint32_t i = 0; i < aClipRects1.Length(); ++i) {
+ for (uint32_t j = 0; j < aClipRects2.Length(); ++j) {
+ if ((aClipRects1[i] + offsetDelta).Intersects(aClipRects2[j])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Given a list of plugin windows to move to new locations, sort the list
+ * so that for each window move, the window moves to a location that
+ * does not intersect other windows. This minimizes flicker and repainting.
+ * It's not always possible to do this perfectly, since in general
+ * we might have cycles. But we do our best.
+ * We need to take into account that windows are clipped to particular
+ * regions and the clip regions change as the windows are moved.
+ */
+static void
+SortConfigurations(nsTArray<nsIWidget::Configuration>* aConfigurations)
+{
+ if (aConfigurations->Length() > 10) {
+ // Give up, we don't want to get bogged down here
+ return;
+ }
+
+ nsTArray<nsIWidget::Configuration> pluginsToMove;
+ pluginsToMove.SwapElements(*aConfigurations);
+
+ // Our algorithm is quite naive. At each step we try to identify
+ // a window that can be moved to its new location that won't overlap
+ // any other windows at the new location. If there is no such
+ // window, we just move the last window in the list anyway.
+ while (!pluginsToMove.IsEmpty()) {
+ // Find a window whose destination does not overlap any other window
+ uint32_t i;
+ for (i = 0; i + 1 < pluginsToMove.Length(); ++i) {
+ nsIWidget::Configuration* config = &pluginsToMove[i];
+ bool foundOverlap = false;
+ for (uint32_t j = 0; j < pluginsToMove.Length(); ++j) {
+ if (i == j)
+ continue;
+ LayoutDeviceIntRect bounds = pluginsToMove[j].mChild->GetBounds();
+ AutoTArray<LayoutDeviceIntRect,1> clipRects;
+ pluginsToMove[j].mChild->GetWindowClipRegion(&clipRects);
+ if (HasOverlap(bounds.TopLeft(), clipRects,
+ config->mBounds.TopLeft(),
+ config->mClipRegion)) {
+ foundOverlap = true;
+ break;
+ }
+ }
+ if (!foundOverlap)
+ break;
+ }
+ // Note that we always move the last plugin in pluginsToMove, if we
+ // can't find any other plugin to move
+ aConfigurations->AppendElement(pluginsToMove[i]);
+ pluginsToMove.RemoveElementAt(i);
+ }
+}
+
+static void
+PluginGetGeometryUpdate(nsTHashtable<nsRefPtrHashKey<nsIContent>>& aPlugins,
+ nsTArray<nsIWidget::Configuration>* aConfigurations)
+{
+ for (auto iter = aPlugins.Iter(); !iter.Done(); iter.Next()) {
+ auto f = static_cast<nsPluginFrame*>(iter.Get()->GetKey()->GetPrimaryFrame());
+ if (!f) {
+ NS_WARNING("Null frame in PluginGeometryUpdate");
+ continue;
+ }
+ f->GetWidgetConfiguration(aConfigurations);
+ }
+}
+
+#endif // #ifndef XP_MACOSX
+
+static void
+PluginDidSetGeometry(nsTHashtable<nsRefPtrHashKey<nsIContent>>& aPlugins)
+{
+ for (auto iter = aPlugins.Iter(); !iter.Done(); iter.Next()) {
+ auto f = static_cast<nsPluginFrame*>(iter.Get()->GetKey()->GetPrimaryFrame());
+ if (!f) {
+ NS_WARNING("Null frame in PluginDidSetGeometry");
+ continue;
+ }
+ f->DidSetWidgetGeometry();
+ }
+}
+
+void
+nsRootPresContext::ApplyPluginGeometryUpdates()
+{
+#ifndef XP_MACOSX
+ CancelApplyPluginGeometryTimer();
+
+ nsTArray<nsIWidget::Configuration> configurations;
+ PluginGetGeometryUpdate(mRegisteredPlugins, &configurations);
+ // Walk mRegisteredPlugins and ask each plugin for its configuration
+ if (!configurations.IsEmpty()) {
+ nsIWidget* widget = configurations[0].mChild->GetParent();
+ NS_ASSERTION(widget, "Plugins must have a parent window");
+ SortConfigurations(&configurations);
+ widget->ConfigureChildren(configurations);
+ }
+#endif // #ifndef XP_MACOSX
+
+ PluginDidSetGeometry(mRegisteredPlugins);
+}
+
+void
+nsRootPresContext::CollectPluginGeometryUpdates(LayerManager* aLayerManager)
+{
+#ifndef XP_MACOSX
+ // Collect and pass plugin widget configurations down to the compositor
+ // for transmission to the chrome process.
+ NS_ASSERTION(aLayerManager, "layer manager is invalid!");
+ mozilla::layers::ClientLayerManager* clm = aLayerManager->AsClientLayerManager();
+
+ nsTArray<nsIWidget::Configuration> configurations;
+ // If there aren't any plugins to configure, clear the plugin data cache
+ // in the layer system.
+ if (!mRegisteredPlugins.Count() && clm) {
+ clm->StorePluginWidgetConfigurations(configurations);
+ return;
+ }
+ PluginGetGeometryUpdate(mRegisteredPlugins, &configurations);
+ if (configurations.IsEmpty()) {
+ PluginDidSetGeometry(mRegisteredPlugins);
+ return;
+ }
+ SortConfigurations(&configurations);
+ if (clm) {
+ clm->StorePluginWidgetConfigurations(configurations);
+ }
+ PluginDidSetGeometry(mRegisteredPlugins);
+#endif // #ifndef XP_MACOSX
+}
+
+static void
+NotifyDidPaintForSubtreeCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsPresContext* presContext = (nsPresContext*)aClosure;
+ nsAutoScriptBlocker blockScripts;
+ // This is a fallback if we don't get paint events for some reason
+ // so we'll just pretend both layer painting and compositing happened.
+ presContext->NotifyDidPaintForSubtree(
+ nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_COMPOSITE);
+}
+
+void
+nsRootPresContext::EnsureEventualDidPaintEvent()
+{
+ if (mNotifyDidPaintTimer)
+ return;
+
+ mNotifyDidPaintTimer = CreateTimer(NotifyDidPaintForSubtreeCallback, 100);
+}
+
+void
+nsRootPresContext::AddWillPaintObserver(nsIRunnable* aRunnable)
+{
+ if (!mWillPaintFallbackEvent.IsPending()) {
+ mWillPaintFallbackEvent = new RunWillPaintObservers(this);
+ NS_DispatchToMainThread(mWillPaintFallbackEvent.get());
+ }
+ mWillPaintObservers.AppendElement(aRunnable);
+}
+
+/**
+ * Run all runnables that need to get called before the next paint.
+ */
+void
+nsRootPresContext::FlushWillPaintObservers()
+{
+ mWillPaintFallbackEvent = nullptr;
+ nsTArray<nsCOMPtr<nsIRunnable> > observers;
+ observers.SwapElements(mWillPaintObservers);
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->Run();
+ }
+}
+
+size_t
+nsRootPresContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return nsPresContext::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mNotifyDidPaintTimer
+ // - mRegisteredPlugins
+ // - mWillPaintObservers
+ // - mWillPaintFallbackEvent
+}
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
new file mode 100644
index 000000000..4fdc60a2e
--- /dev/null
+++ b/layout/base/nsPresContext.h
@@ -0,0 +1,1588 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a presentation of a document, part 1 */
+
+#ifndef nsPresContext_h___
+#define nsPresContext_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/WeakPtr.h"
+#include "nsColor.h"
+#include "nsCoord.h"
+#include "nsCOMPtr.h"
+#include "nsIPresShell.h"
+#include "nsRect.h"
+#include "nsFont.h"
+#include "gfxFontConstants.h"
+#include "nsIAtom.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsCRT.h"
+#include "nsIWidgetListener.h"
+#include "FramePropertyTable.h"
+#include "nsGkAtoms.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsChangeHint.h"
+#include <algorithm>
+// This also pulls in gfxTypes.h, which we cannot include directly.
+#include "gfxRect.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/AppUnits.h"
+#include "prclist.h"
+#include "nsThreadUtils.h"
+#include "ScrollbarStyles.h"
+#include "nsIMessageManager.h"
+#include "mozilla/RestyleLogging.h"
+#include "Units.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "prenv.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/StyleBackendType.h"
+
+class nsAString;
+class nsIPrintSettings;
+class nsDocShell;
+class nsIDocShell;
+class nsIDocument;
+class nsILanguageAtomService;
+class nsITheme;
+class nsIContent;
+class nsIFrame;
+class nsFrameManager;
+class nsILinkHandler;
+class nsIAtom;
+class nsIRunnable;
+class gfxUserFontEntry;
+class gfxUserFontSet;
+class gfxTextPerfMetrics;
+class nsPluginFrame;
+class nsTransitionManager;
+class nsAnimationManager;
+class nsRefreshDriver;
+class nsIWidget;
+class nsDeviceContext;
+class gfxMissingFontRecorder;
+
+namespace mozilla {
+class EffectCompositor;
+class EventStateManager;
+class CounterStyleManager;
+namespace layers {
+class ContainerLayer;
+class LayerManager;
+} // namespace layers
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+// supported values for cached bool types
+enum nsPresContext_CachedBoolPrefType {
+ kPresContext_UseDocumentFonts = 1,
+ kPresContext_UnderlineLinks
+};
+
+// supported values for cached integer pref types
+enum nsPresContext_CachedIntPrefType {
+ kPresContext_ScrollbarSide = 1,
+ kPresContext_BidiDirection
+};
+
+// IDs for the default variable and fixed fonts (not to be changed, see nsFont.h)
+// To be used for Get/SetDefaultFont(). The other IDs in nsFont.h are also supported.
+const uint8_t kPresContext_DefaultVariableFont_ID = 0x00; // kGenericFont_moz_variable
+const uint8_t kPresContext_DefaultFixedFont_ID = 0x01; // kGenericFont_moz_fixed
+
+#ifdef DEBUG
+struct nsAutoLayoutPhase;
+
+enum nsLayoutPhase {
+ eLayoutPhase_Paint,
+ eLayoutPhase_Reflow,
+ eLayoutPhase_FrameC,
+ eLayoutPhase_COUNT
+};
+#endif
+
+class nsInvalidateRequestList {
+public:
+ struct Request {
+ nsRect mRect;
+ uint32_t mFlags;
+ };
+
+ void TakeFrom(nsInvalidateRequestList* aList)
+ {
+ mRequests.AppendElements(mozilla::Move(aList->mRequests));
+ }
+ bool IsEmpty() { return mRequests.IsEmpty(); }
+
+ nsTArray<Request> mRequests;
+};
+
+/* Used by nsPresContext::HasAuthorSpecifiedRules */
+#define NS_AUTHOR_SPECIFIED_BACKGROUND (1 << 0)
+#define NS_AUTHOR_SPECIFIED_BORDER (1 << 1)
+#define NS_AUTHOR_SPECIFIED_PADDING (1 << 2)
+#define NS_AUTHOR_SPECIFIED_TEXT_SHADOW (1 << 3)
+
+class nsRootPresContext;
+
+// An interface for presentation contexts. Presentation contexts are
+// objects that provide an outer context for a presentation shell.
+
+class nsPresContext : public nsIObserver,
+ public mozilla::SupportsWeakPtr<nsPresContext> {
+public:
+ typedef mozilla::FramePropertyTable FramePropertyTable;
+ typedef mozilla::LangGroupFontPrefs LangGroupFontPrefs;
+ typedef mozilla::ScrollbarStyles ScrollbarStyles;
+ typedef mozilla::StaticPresData StaticPresData;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPresContext)
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsPresContext)
+
+ enum nsPresContextType {
+ eContext_Galley, // unpaginated screen presentation
+ eContext_PrintPreview, // paginated screen presentation
+ eContext_Print, // paginated printer presentation
+ eContext_PageLayout // paginated & editable.
+ };
+
+ nsPresContext(nsIDocument* aDocument, nsPresContextType aType);
+
+ /**
+ * Initialize the presentation context from a particular device.
+ */
+ nsresult Init(nsDeviceContext* aDeviceContext);
+
+ /**
+ * Set and detach presentation shell that this context is bound to.
+ * A presentation context may only be bound to a single shell.
+ */
+ void AttachShell(nsIPresShell* aShell, mozilla::StyleBackendType aBackendType);
+ void DetachShell();
+
+
+ nsPresContextType Type() const { return mType; }
+
+ /**
+ * Get the PresentationShell that this context is bound to.
+ */
+ nsIPresShell* PresShell() const
+ {
+ NS_ASSERTION(mShell, "Null pres shell");
+ return mShell;
+ }
+
+ nsIPresShell* GetPresShell() const { return mShell; }
+
+ /**
+ * Returns the parent prescontext for this one. Returns null if this is a
+ * root.
+ */
+ nsPresContext* GetParentPresContext();
+
+ /**
+ * Returns the prescontext of the toplevel content document that contains
+ * this presentation, or null if there isn't one.
+ */
+ nsPresContext* GetToplevelContentDocumentPresContext();
+
+ /**
+ * Returns the nearest widget for the root frame of this.
+ *
+ * @param aOffset If non-null the offset from the origin of the root
+ * frame's view to the widget's origin (usually positive)
+ * expressed in appunits of this will be returned in
+ * aOffset.
+ */
+ nsIWidget* GetNearestWidget(nsPoint* aOffset = nullptr);
+
+ /**
+ * Returns the root widget for this.
+ * Note that the widget is a mediater with IME.
+ */
+ nsIWidget* GetRootWidget();
+
+ /**
+ * Return the presentation context for the root of the view manager
+ * hierarchy that contains this presentation context, or nullptr if it can't
+ * be found (e.g. it's detached).
+ */
+ nsRootPresContext* GetRootPresContext();
+
+ virtual bool IsRoot() { return false; }
+
+ nsIDocument* Document() const
+ {
+ NS_ASSERTION(!mShell || !mShell->GetDocument() ||
+ mShell->GetDocument() == mDocument,
+ "nsPresContext doesn't have the same document as nsPresShell!");
+ return mDocument;
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ mozilla::StyleSetHandle StyleSet() { return GetPresShell()->StyleSet(); }
+
+ nsFrameManager* FrameManager()
+ { return PresShell()->FrameManager(); }
+
+ nsCSSFrameConstructor* FrameConstructor()
+ { return PresShell()->FrameConstructor(); }
+
+ mozilla::EffectCompositor* EffectCompositor() { return mEffectCompositor; }
+ nsTransitionManager* TransitionManager() { return mTransitionManager; }
+ nsAnimationManager* AnimationManager() { return mAnimationManager; }
+
+ nsRefreshDriver* RefreshDriver() { return mRefreshDriver; }
+
+ mozilla::RestyleManagerHandle RestyleManager() {
+ MOZ_ASSERT(mRestyleManager);
+ return mRestyleManager;
+ }
+
+ mozilla::CounterStyleManager* CounterStyleManager() {
+ return mCounterStyleManager;
+ }
+#endif
+
+ /**
+ * Rebuilds all style data by throwing out the old rule tree and
+ * building a new one, and additionally applying aExtraHint (which
+ * must not contain nsChangeHint_ReconstructFrame) to the root frame.
+ * For aRestyleHint, see RestyleManager::RebuildAllStyleData.
+ * Also rebuild the user font set and counter style manager.
+ */
+ void RebuildAllStyleData(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint);
+ /**
+ * Just like RebuildAllStyleData, except (1) asynchronous and (2) it
+ * doesn't rebuild the user font set.
+ */
+ void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint);
+
+ /**
+ * Handle changes in the values of media features (used in media
+ * queries).
+ *
+ * There are three sensible values to use for aRestyleHint:
+ * * nsRestyleHint(0) to rebuild style data, with rerunning of
+ * selector matching, only if media features have changed
+ * * eRestyle_ForceDescendants to force rebuilding of style data (but
+ * still only rerun selector matching if media query results have
+ * changed). (RebuildAllStyleData always adds
+ * eRestyle_ForceDescendants internally, so here we're only using
+ * it to distinguish from nsRestyleHint(0) whether we need to call
+ * RebuildAllStyleData at all.)
+ * * eRestyle_Subtree to force rebuilding of style data with
+ * rerunning of selector matching
+ *
+ * For aChangeHint, see RestyleManager::RebuildAllStyleData. (Passing
+ * a nonzero aChangeHint forces rebuilding style data even if
+ * nsRestyleHint(0) is passed.)
+ */
+ void MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint = nsChangeHint(0));
+ /**
+ * Calls MediaFeatureValuesChanged for this pres context and all descendant
+ * subdocuments that have a pres context. This should be used for media
+ * features that must be updated in all subdocuments e.g. display-mode.
+ */
+ void MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint = nsChangeHint(0));
+
+ void PostMediaFeatureValuesChangedEvent();
+ void HandleMediaFeatureValuesChangedEvent();
+ void FlushPendingMediaFeatureValuesChanged() {
+ if (mPendingMediaFeatureValuesChanged)
+ MediaFeatureValuesChanged(nsRestyleHint(0));
+ }
+
+ /**
+ * Updates the size mode on all remote children and recursively notifies this
+ * document and all subdocuments (including remote children) that a media
+ * feature value has changed.
+ */
+ void SizeModeChanged(nsSizeMode aSizeMode);
+
+ /**
+ * Access compatibility mode for this context. This is the same as
+ * our document's compatibility mode.
+ */
+ nsCompatibility CompatibilityMode() const;
+
+ /**
+ * Notify the context that the document's compatibility mode has changed
+ */
+ void CompatibilityModeChanged();
+
+ /**
+ * Access the image animation mode for this context
+ */
+ uint16_t ImageAnimationMode() const { return mImageAnimationMode; }
+ virtual void SetImageAnimationModeExternal(uint16_t aMode);
+ void SetImageAnimationModeInternal(uint16_t aMode);
+#ifdef MOZILLA_INTERNAL_API
+ void SetImageAnimationMode(uint16_t aMode)
+ { SetImageAnimationModeInternal(aMode); }
+#else
+ void SetImageAnimationMode(uint16_t aMode)
+ { SetImageAnimationModeExternal(aMode); }
+#endif
+
+ /**
+ * Get medium of presentation
+ */
+ nsIAtom* Medium() {
+ if (!mIsEmulatingMedia)
+ return mMedium;
+ return mMediaEmulated;
+ }
+
+ /*
+ * Render the document as if being viewed on a device with the specified
+ * media type.
+ */
+ void EmulateMedium(const nsAString& aMediaType);
+
+ /*
+ * Restore the viewer's natural medium
+ */
+ void StopEmulatingMedium();
+
+ void* AllocateFromShell(size_t aSize)
+ {
+ if (mShell)
+ return mShell->AllocateMisc(aSize);
+ return nullptr;
+ }
+
+ void FreeToShell(size_t aSize, void* aFreeChunk)
+ {
+ NS_ASSERTION(mShell, "freeing after shutdown");
+ if (mShell)
+ mShell->FreeMisc(aSize, aFreeChunk);
+ }
+
+ /**
+ * Get the default font for the given language and generic font ID.
+ * If aLanguage is nullptr, the document's language is used.
+ *
+ * See the comment in StaticPresData::GetDefaultFont.
+ */
+ const nsFont* GetDefaultFont(uint8_t aFontID,
+ nsIAtom *aLanguage) const
+ {
+ nsIAtom* lang = aLanguage ? aLanguage : mLanguage.get();
+ return StaticPresData::Get()->GetDefaultFontHelper(aFontID, lang,
+ GetFontPrefsForLang(lang));
+ }
+
+ /** Get a cached boolean pref, by its type */
+ // * - initially created for bugs 31816, 20760, 22963
+ bool GetCachedBoolPref(nsPresContext_CachedBoolPrefType aPrefType) const
+ {
+ // If called with a constant parameter, the compiler should optimize
+ // this switch statement away.
+ switch (aPrefType) {
+ case kPresContext_UseDocumentFonts:
+ return mUseDocumentFonts;
+ case kPresContext_UnderlineLinks:
+ return mUnderlineLinks;
+ default:
+ NS_ERROR("Invalid arg passed to GetCachedBoolPref");
+ }
+
+ return false;
+ }
+
+ /** Get a cached integer pref, by its type */
+ // * - initially created for bugs 30910, 61883, 74186, 84398
+ int32_t GetCachedIntPref(nsPresContext_CachedIntPrefType aPrefType) const
+ {
+ // If called with a constant parameter, the compiler should optimize
+ // this switch statement away.
+ switch (aPrefType) {
+ case kPresContext_ScrollbarSide:
+ return mPrefScrollbarSide;
+ case kPresContext_BidiDirection:
+ return mPrefBidiDirection;
+ default:
+ NS_ERROR("invalid arg passed to GetCachedIntPref");
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the default colors
+ */
+ nscolor DefaultColor() const { return mDefaultColor; }
+ nscolor DefaultBackgroundColor() const { return mBackgroundColor; }
+ nscolor DefaultLinkColor() const { return mLinkColor; }
+ nscolor DefaultActiveLinkColor() const { return mActiveLinkColor; }
+ nscolor DefaultVisitedLinkColor() const { return mVisitedLinkColor; }
+ nscolor FocusBackgroundColor() const { return mFocusBackgroundColor; }
+ nscolor FocusTextColor() const { return mFocusTextColor; }
+
+ /**
+ * Body text color, for use in quirks mode only.
+ */
+ nscolor BodyTextColor() const { return mBodyTextColor; }
+ void SetBodyTextColor(nscolor aColor) { mBodyTextColor = aColor; }
+
+ bool GetUseFocusColors() const { return mUseFocusColors; }
+ uint8_t FocusRingWidth() const { return mFocusRingWidth; }
+ bool GetFocusRingOnAnything() const { return mFocusRingOnAnything; }
+ uint8_t GetFocusRingStyle() const { return mFocusRingStyle; }
+
+ void SetContainer(nsIDocShell* aContainer);
+
+ virtual nsISupports* GetContainerWeakExternal() const;
+ nsISupports* GetContainerWeakInternal() const;
+#ifdef MOZILLA_INTERNAL_API
+ nsISupports* GetContainerWeak() const
+ { return GetContainerWeakInternal(); }
+#else
+ nsISupports* GetContainerWeak() const
+ { return GetContainerWeakExternal(); }
+#endif
+
+ nsIDocShell* GetDocShell() const;
+
+ // XXX this are going to be replaced with set/get container
+ void SetLinkHandler(nsILinkHandler* aHandler) { mLinkHandler = aHandler; }
+ nsILinkHandler* GetLinkHandler() { return mLinkHandler; }
+
+ /**
+ * Detach this pres context - i.e. cancel relevant timers,
+ * SetLinkHandler(null), SetContainer(null) etc.
+ * Only to be used by the DocumentViewer.
+ */
+ virtual void Detach();
+
+ /**
+ * Get the visible area associated with this presentation context.
+ * This is the size of the visible area that is used for
+ * presenting the document. The returned value is in the standard
+ * nscoord units (as scaled by the device context).
+ */
+ nsRect GetVisibleArea() const { return mVisibleArea; }
+
+ /**
+ * Set the currently visible area. The units for r are standard
+ * nscoord units (as scaled by the device context).
+ */
+ void SetVisibleArea(const nsRect& r) {
+ if (!r.IsEqualEdges(mVisibleArea)) {
+ mVisibleArea = r;
+ // Visible area does not affect media queries when paginated.
+ if (!IsPaginated() && HasCachedStyleData()) {
+ mPendingViewportChange = true;
+ PostMediaFeatureValuesChangedEvent();
+ }
+ }
+ }
+
+ /**
+ * Return true if this presentation context is a paginated
+ * context.
+ */
+ bool IsPaginated() const { return mPaginated; }
+
+ /**
+ * Sets whether the presentation context can scroll for a paginated
+ * context.
+ */
+ void SetPaginatedScrolling(bool aResult);
+
+ /**
+ * Return true if this presentation context can scroll for paginated
+ * context.
+ */
+ bool HasPaginatedScrolling() const { return mCanPaginatedScroll; }
+
+ /**
+ * Get/set the size of a page
+ */
+ nsSize GetPageSize() { return mPageSize; }
+ void SetPageSize(nsSize aSize) { mPageSize = aSize; }
+
+ /**
+ * Get/set whether this document should be treated as having real pages
+ * XXX This raises the obvious question of why a document that isn't a page
+ * is paginated; there isn't a good reason except history
+ */
+ bool IsRootPaginatedDocument() { return mIsRootPaginatedDocument; }
+ void SetIsRootPaginatedDocument(bool aIsRootPaginatedDocument)
+ { mIsRootPaginatedDocument = aIsRootPaginatedDocument; }
+
+ /**
+ * Get/set the print scaling level; used by nsPageFrame to scale up
+ * pages. Set safe to call before reflow, get guaranteed to be set
+ * properly after reflow.
+ */
+
+ float GetPageScale() { return mPageScale; }
+ void SetPageScale(float aScale) { mPageScale = aScale; }
+
+ /**
+ * Get/set the scaling facor to use when rendering the pages for print preview.
+ * Only safe to get after print preview set up; safe to set anytime.
+ * This is a scaling factor for the display of the print preview. It
+ * does not affect layout. It only affects the size of the onscreen pages
+ * in print preview.
+ * XXX Temporary: see http://wiki.mozilla.org/Gecko:PrintPreview
+ */
+ float GetPrintPreviewScale() { return mPPScale; }
+ void SetPrintPreviewScale(float aScale) { mPPScale = aScale; }
+
+ nsDeviceContext* DeviceContext() { return mDeviceContext; }
+ mozilla::EventStateManager* EventStateManager() { return mEventManager; }
+ nsIAtom* GetLanguageFromCharset() const { return mLanguage; }
+ already_AddRefed<nsIAtom> GetContentLanguage() const;
+
+ float TextZoom() { return mTextZoom; }
+ void SetTextZoom(float aZoom) {
+ MOZ_ASSERT(aZoom > 0.0f, "invalid zoom factor");
+ if (aZoom == mTextZoom)
+ return;
+
+ mTextZoom = aZoom;
+ if (HasCachedStyleData()) {
+ // Media queries could have changed, since we changed the meaning
+ // of 'em' units in them.
+ MediaFeatureValuesChanged(eRestyle_ForceDescendants,
+ NS_STYLE_HINT_REFLOW);
+ }
+ }
+
+ /**
+ * Get the minimum font size for the specified language. If aLanguage
+ * is nullptr, then the document's language is used. This combines
+ * the language-specific global preference with the per-presentation
+ * base minimum font size.
+ */
+ int32_t MinFontSize(nsIAtom *aLanguage) const {
+ const LangGroupFontPrefs *prefs = GetFontPrefsForLang(aLanguage);
+ return std::max(mBaseMinFontSize, prefs->mMinimumFontSize);
+ }
+
+ /**
+ * Get the per-presentation base minimum font size. This size is
+ * independent of the language-specific global preference.
+ */
+ int32_t BaseMinFontSize() const {
+ return mBaseMinFontSize;
+ }
+
+ /**
+ * Set the per-presentation base minimum font size. This size is
+ * independent of the language-specific global preference.
+ */
+ void SetBaseMinFontSize(int32_t aMinFontSize) {
+ if (aMinFontSize == mBaseMinFontSize)
+ return;
+
+ mBaseMinFontSize = aMinFontSize;
+ if (HasCachedStyleData()) {
+ // Media queries could have changed, since we changed the meaning
+ // of 'em' units in them.
+ MediaFeatureValuesChanged(eRestyle_ForceDescendants,
+ NS_STYLE_HINT_REFLOW);
+ }
+ }
+
+ float GetFullZoom() { return mFullZoom; }
+ void SetFullZoom(float aZoom);
+
+ float GetOverrideDPPX() { return mOverrideDPPX; }
+ void SetOverrideDPPX(float aDPPX);
+
+ nscoord GetAutoQualityMinFontSize() {
+ return DevPixelsToAppUnits(mAutoQualityMinFontSizePixelsPref);
+ }
+
+ /**
+ * Return the device's screen size in inches, for font size
+ * inflation.
+ *
+ * If |aChanged| is non-null, then aChanged is filled in with whether
+ * the screen size value has changed since either:
+ * a. the last time the function was called with non-null aChanged, or
+ * b. the first time the function was called.
+ */
+ gfxSize ScreenSizeInchesForFontInflation(bool* aChanged = nullptr);
+
+ static int32_t AppUnitsPerCSSPixel() { return mozilla::AppUnitsPerCSSPixel(); }
+ int32_t AppUnitsPerDevPixel() const;
+ static int32_t AppUnitsPerCSSInch() { return mozilla::AppUnitsPerCSSInch(); }
+
+ static nscoord CSSPixelsToAppUnits(int32_t aPixels)
+ { return NSToCoordRoundWithClamp(float(aPixels) *
+ float(AppUnitsPerCSSPixel())); }
+
+ static nscoord CSSPixelsToAppUnits(float aPixels)
+ { return NSToCoordRoundWithClamp(aPixels *
+ float(AppUnitsPerCSSPixel())); }
+
+ static int32_t AppUnitsToIntCSSPixels(nscoord aAppUnits)
+ { return NSAppUnitsToIntPixels(aAppUnits,
+ float(AppUnitsPerCSSPixel())); }
+
+ static float AppUnitsToFloatCSSPixels(nscoord aAppUnits)
+ { return NSAppUnitsToFloatPixels(aAppUnits,
+ float(AppUnitsPerCSSPixel())); }
+
+ static double AppUnitsToDoubleCSSPixels(nscoord aAppUnits)
+ { return NSAppUnitsToDoublePixels(aAppUnits,
+ double(AppUnitsPerCSSPixel())); }
+
+ nscoord DevPixelsToAppUnits(int32_t aPixels) const
+ { return NSIntPixelsToAppUnits(aPixels, AppUnitsPerDevPixel()); }
+
+ int32_t AppUnitsToDevPixels(nscoord aAppUnits) const
+ { return NSAppUnitsToIntPixels(aAppUnits,
+ float(AppUnitsPerDevPixel())); }
+
+ float AppUnitsToFloatDevPixels(nscoord aAppUnits)
+ { return aAppUnits / float(AppUnitsPerDevPixel()); }
+
+ int32_t CSSPixelsToDevPixels(int32_t aPixels)
+ { return AppUnitsToDevPixels(CSSPixelsToAppUnits(aPixels)); }
+
+ float CSSPixelsToDevPixels(float aPixels)
+ {
+ return NSAppUnitsToFloatPixels(CSSPixelsToAppUnits(aPixels),
+ float(AppUnitsPerDevPixel()));
+ }
+
+ int32_t DevPixelsToIntCSSPixels(int32_t aPixels)
+ { return AppUnitsToIntCSSPixels(DevPixelsToAppUnits(aPixels)); }
+
+ float DevPixelsToFloatCSSPixels(int32_t aPixels)
+ { return AppUnitsToFloatCSSPixels(DevPixelsToAppUnits(aPixels)); }
+
+ mozilla::CSSToLayoutDeviceScale CSSToDevPixelScale() const
+ {
+ return mozilla::CSSToLayoutDeviceScale(
+ float(AppUnitsPerCSSPixel()) / float(AppUnitsPerDevPixel()));
+ }
+
+ // If there is a remainder, it is rounded to nearest app units.
+ nscoord GfxUnitsToAppUnits(gfxFloat aGfxUnits) const;
+
+ gfxFloat AppUnitsToGfxUnits(nscoord aAppUnits) const;
+
+ gfxRect AppUnitsToGfxUnits(const nsRect& aAppRect) const
+ { return gfxRect(AppUnitsToGfxUnits(aAppRect.x),
+ AppUnitsToGfxUnits(aAppRect.y),
+ AppUnitsToGfxUnits(aAppRect.width),
+ AppUnitsToGfxUnits(aAppRect.height)); }
+
+ static nscoord CSSTwipsToAppUnits(float aTwips)
+ { return NSToCoordRoundWithClamp(
+ mozilla::AppUnitsPerCSSInch() * NS_TWIPS_TO_INCHES(aTwips)); }
+
+ // Margin-specific version, since they often need TwipsToAppUnits
+ static nsMargin CSSTwipsToAppUnits(const nsIntMargin &marginInTwips)
+ { return nsMargin(CSSTwipsToAppUnits(float(marginInTwips.top)),
+ CSSTwipsToAppUnits(float(marginInTwips.right)),
+ CSSTwipsToAppUnits(float(marginInTwips.bottom)),
+ CSSTwipsToAppUnits(float(marginInTwips.left))); }
+
+ static nscoord CSSPointsToAppUnits(float aPoints)
+ { return NSToCoordRound(aPoints * mozilla::AppUnitsPerCSSInch() /
+ POINTS_PER_INCH_FLOAT); }
+
+ nscoord RoundAppUnitsToNearestDevPixels(nscoord aAppUnits) const
+ { return DevPixelsToAppUnits(AppUnitsToDevPixels(aAppUnits)); }
+
+ /**
+ * This checks the root element and the HTML BODY, if any, for an "overflow"
+ * property that should be applied to the viewport. If one is found then we
+ * return the element that we took the overflow from (which should then be
+ * treated as "overflow: visible"), and we store the overflow style here.
+ * If the document is in fullscreen, and the fullscreen element is not the
+ * root, the scrollbar of viewport will be suppressed.
+ * @return if scroll was propagated from some content node, the content node
+ * it was propagated from.
+ */
+ nsIContent* UpdateViewportScrollbarStylesOverride();
+ const ScrollbarStyles& GetViewportScrollbarStylesOverride()
+ {
+ return mViewportStyleScrollbar;
+ }
+
+ /**
+ * Check whether the given element would propagate its scrollbar styles to the
+ * viewport in non-paginated mode. Must only be called if IsPaginated().
+ */
+ bool ElementWouldPropagateScrollbarStyles(mozilla::dom::Element* aElement);
+
+ /**
+ * Set and get methods for controlling the background drawing
+ */
+ bool GetBackgroundImageDraw() const { return mDrawImageBackground; }
+ void SetBackgroundImageDraw(bool aCanDraw)
+ {
+ mDrawImageBackground = aCanDraw;
+ }
+
+ bool GetBackgroundColorDraw() const { return mDrawColorBackground; }
+ void SetBackgroundColorDraw(bool aCanDraw)
+ {
+ mDrawColorBackground = aCanDraw;
+ }
+
+ /**
+ * Check if bidi enabled (set depending on the presence of RTL
+ * characters or when default directionality is RTL).
+ * If enabled, we should apply the Unicode Bidi Algorithm
+ *
+ * @lina 07/12/2000
+ */
+#ifdef MOZILLA_INTERNAL_API
+ bool BidiEnabled() const { return BidiEnabledInternal(); }
+#else
+ bool BidiEnabled() const { return BidiEnabledExternal(); }
+#endif
+ virtual bool BidiEnabledExternal() const;
+ bool BidiEnabledInternal() const;
+
+ /**
+ * Set bidi enabled. This means we should apply the Unicode Bidi Algorithm
+ *
+ * @lina 07/12/2000
+ */
+ void SetBidiEnabled() const;
+
+ /**
+ * Set visual or implicit mode into the pres context.
+ *
+ * Visual directionality is a presentation method that displays text
+ * as if it were a uni-directional, according to the primary display
+ * direction only.
+ *
+ * Implicit directionality is a presentation method in which the
+ * direction is determined by the Bidi algorithm according to the
+ * category of the characters and the category of the adjacent
+ * characters, and according to their primary direction.
+ *
+ * @lina 05/02/2000
+ */
+ void SetVisualMode(bool aIsVisual)
+ {
+ mIsVisual = aIsVisual;
+ }
+
+ /**
+ * Check whether the content should be treated as visual.
+ *
+ * @lina 05/02/2000
+ */
+ bool IsVisualMode() const { return mIsVisual; }
+
+//Mohamed
+
+ /**
+ * Set the Bidi options for the presentation context
+ */
+ void SetBidi(uint32_t aBidiOptions,
+ bool aForceRestyle = false);
+
+ /**
+ * Get the Bidi options for the presentation context
+ * Not inline so consumers of nsPresContext are not forced to
+ * include nsIDocument.
+ */
+ uint32_t GetBidi() const;
+
+ /**
+ * Render only Selection
+ */
+ void SetIsRenderingOnlySelection(bool aResult)
+ {
+ mIsRenderingOnlySelection = aResult;
+ }
+
+ bool IsRenderingOnlySelection() const { return mIsRenderingOnlySelection; }
+
+ bool IsTopLevelWindowInactive();
+
+ /*
+ * Obtain a native them for rendering our widgets (both form controls and html)
+ */
+ nsITheme* GetTheme();
+
+ /*
+ * Notify the pres context that the theme has changed. An internal switch
+ * means it's one of our Mozilla themes that changed (e.g., Modern to Classic).
+ * Otherwise, the OS is telling us that the native theme for the platform
+ * has changed.
+ */
+ void ThemeChanged();
+
+ /*
+ * Notify the pres context that the resolution of the user interface has
+ * changed. This happens if a window is moved between HiDPI and non-HiDPI
+ * displays, so that the ratio of points to device pixels changes.
+ * The notification happens asynchronously.
+ */
+ void UIResolutionChanged();
+
+ /*
+ * Like UIResolutionChanged() but invalidates values immediately.
+ */
+ void UIResolutionChangedSync();
+
+ /*
+ * Notify the pres context that a system color has changed
+ */
+ void SysColorChanged();
+
+ /** Printing methods below should only be used for Medium() == print **/
+ void SetPrintSettings(nsIPrintSettings *aPrintSettings);
+
+ nsIPrintSettings* GetPrintSettings() { return mPrintSettings; }
+
+ /* Accessor for table of frame properties */
+ FramePropertyTable* PropertyTable() { return &mPropertyTable; }
+
+ /* Helper function that ensures that this prescontext is shown in its
+ docshell if it's the most recent prescontext for the docshell. Returns
+ whether the prescontext is now being shown.
+ */
+ bool EnsureVisible();
+
+#ifdef MOZ_REFLOW_PERF
+ void CountReflows(const char * aName,
+ nsIFrame * aFrame);
+#endif
+
+ void RestyledElement() {
+ ++mElementsRestyled;
+ }
+ void ConstructedFrame() {
+ ++mFramesConstructed;
+ }
+ void ReflowedFrame() {
+ ++mFramesReflowed;
+ }
+
+ uint64_t ElementsRestyledCount() {
+ return mElementsRestyled;
+ }
+ uint64_t FramesConstructedCount() {
+ return mFramesConstructed;
+ }
+ uint64_t FramesReflowedCount() {
+ return mFramesReflowed;
+ }
+
+ /**
+ * This table maps border-width enums 'thin', 'medium', 'thick'
+ * to actual nscoord values.
+ */
+ const nscoord* GetBorderWidthTable() { return mBorderWidthTable; }
+
+ gfxTextPerfMetrics *GetTextPerfMetrics() { return mTextPerf; }
+
+ bool IsDynamic() { return (mType == eContext_PageLayout || mType == eContext_Galley); }
+ bool IsScreen() { return (mMedium == nsGkAtoms::screen ||
+ mType == eContext_PageLayout ||
+ mType == eContext_PrintPreview); }
+
+ // Is this presentation in a chrome docshell?
+ bool IsChrome() const { return mIsChrome; }
+ bool IsChromeOriginImage() const { return mIsChromeOriginImage; }
+ void UpdateIsChrome();
+
+ // Public API for native theme code to get style internals.
+ bool HasAuthorSpecifiedRules(const nsIFrame *aFrame,
+ uint32_t ruleTypeMask) const;
+
+ // Is it OK to let the page specify colors and backgrounds?
+ bool UseDocumentColors() const {
+ MOZ_ASSERT(mUseDocumentColors || !(IsChrome() || IsChromeOriginImage()),
+ "We should never have a chrome doc or image that can't use its colors.");
+ return mUseDocumentColors;
+ }
+
+ // Explicitly enable and disable paint flashing.
+ void SetPaintFlashing(bool aPaintFlashing) {
+ mPaintFlashing = aPaintFlashing;
+ mPaintFlashingInitialized = true;
+ }
+
+ // This method should be used instead of directly accessing mPaintFlashing,
+ // as that value may be out of date when mPaintFlashingInitialized is false.
+ bool GetPaintFlashing() const;
+
+ bool SuppressingResizeReflow() const { return mSuppressResizeReflow; }
+
+ gfxUserFontSet* GetUserFontSet(bool aFlushUserFontSet = true);
+
+ // Should be called whenever the set of fonts available in the user
+ // font set changes (e.g., because a new font loads, or because the
+ // user font set is changed and fonts become unavailable).
+ void UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont = nullptr);
+
+ gfxMissingFontRecorder *MissingFontRecorder() { return mMissingFonts; }
+ void NotifyMissingFonts();
+
+ void FlushCounterStyles();
+ void RebuildCounterStyles(); // asynchronously
+
+ // Ensure that it is safe to hand out CSS rules outside the layout
+ // engine by ensuring that all CSS style sheets have unique inners
+ // and, if necessary, synchronously rebuilding all style data.
+ void EnsureSafeToHandOutCSSRules();
+
+ void NotifyInvalidation(uint32_t aFlags);
+ void NotifyInvalidation(const nsRect& aRect, uint32_t aFlags);
+ // aRect is in device pixels
+ void NotifyInvalidation(const nsIntRect& aRect, uint32_t aFlags);
+ // aFlags are nsIPresShell::PAINT_ flags
+ void NotifyDidPaintForSubtree(uint32_t aFlags, uint64_t aTransactionId = 0,
+ const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp());
+ void FireDOMPaintEvent(nsInvalidateRequestList* aList, uint64_t aTransactionId);
+
+ // Callback for catching invalidations in ContainerLayers
+ // Passed to LayerProperties::ComputeDifference
+ static void NotifySubDocInvalidation(mozilla::layers::ContainerLayer* aContainer,
+ const nsIntRegion& aRegion);
+ void SetNotifySubDocInvalidationData(mozilla::layers::ContainerLayer* aContainer);
+ static void ClearNotifySubDocInvalidationData(mozilla::layers::ContainerLayer* aContainer);
+ bool IsDOMPaintEventPending();
+ void ClearMozAfterPaintEvents() {
+ mInvalidateRequestsSinceLastPaint.mRequests.Clear();
+ mUndeliveredInvalidateRequestsBeforeLastPaint.mRequests.Clear();
+ mAllInvalidated = false;
+ }
+
+ /**
+ * Returns the RestyleManager's restyle generation counter.
+ */
+ uint64_t GetRestyleGeneration() const;
+
+ /**
+ * Returns whether there are any pending restyles or reflows.
+ */
+ bool HasPendingRestyleOrReflow();
+
+ /**
+ * Informs the document's FontFaceSet that the refresh driver ticked,
+ * flushing style and layout.
+ */
+ void NotifyFontFaceSetOnRefresh();
+
+ /**
+ * Notify the prescontext that the presshell is about to reflow a reflow root.
+ * The single argument indicates whether this reflow should be interruptible.
+ * If aInterruptible is false then CheckForInterrupt and HasPendingInterrupt
+ * will always return false. If aInterruptible is true then CheckForInterrupt
+ * will return true when a pending event is detected. This is for use by the
+ * presshell only. Reflow code wanting to prevent interrupts should use
+ * InterruptPreventer.
+ */
+ void ReflowStarted(bool aInterruptible);
+
+ /**
+ * A class that can be used to temporarily disable reflow interruption.
+ */
+ class InterruptPreventer;
+ friend class InterruptPreventer;
+ class MOZ_STACK_CLASS InterruptPreventer {
+ public:
+ explicit InterruptPreventer(nsPresContext* aCtx) :
+ mCtx(aCtx),
+ mInterruptsEnabled(aCtx->mInterruptsEnabled),
+ mHasPendingInterrupt(aCtx->mHasPendingInterrupt)
+ {
+ mCtx->mInterruptsEnabled = false;
+ mCtx->mHasPendingInterrupt = false;
+ }
+ ~InterruptPreventer() {
+ mCtx->mInterruptsEnabled = mInterruptsEnabled;
+ mCtx->mHasPendingInterrupt = mHasPendingInterrupt;
+ }
+
+ private:
+ nsPresContext* mCtx;
+ bool mInterruptsEnabled;
+ bool mHasPendingInterrupt;
+ };
+
+ /**
+ * Check for interrupts. This may return true if a pending event is
+ * detected. Once it has returned true, it will keep returning true
+ * until ReflowStarted is called. In all cases where this returns true,
+ * the passed-in frame (which should be the frame whose reflow will be
+ * interrupted if true is returned) will be passed to
+ * nsIPresShell::FrameNeedsToContinueReflow.
+ */
+ bool CheckForInterrupt(nsIFrame* aFrame);
+ /**
+ * Returns true if CheckForInterrupt has returned true since the last
+ * ReflowStarted call. Cannot itself trigger an interrupt check.
+ */
+ bool HasPendingInterrupt() { return mHasPendingInterrupt; }
+ /**
+ * Sets a flag that will trip a reflow interrupt. This only bypasses the
+ * interrupt timeout and the pending event check; other checks such as whether
+ * interrupts are enabled and the interrupt check skipping still take effect.
+ */
+ void SetPendingInterruptFromTest() { mPendingInterruptFromTest = true; }
+
+ /**
+ * If we have a presshell, and if the given content's current
+ * document is the same as our presshell's document, return the
+ * content's primary frame. Otherwise, return null. Only use this
+ * if you care about which presshell the primary frame is in.
+ */
+ nsIFrame* GetPrimaryFrameFor(nsIContent* aContent);
+
+ void NotifyDestroyingFrame(nsIFrame* aFrame)
+ {
+ PropertyTable()->DeleteAllFor(aFrame);
+ }
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ bool IsRootContentDocument() const;
+
+ bool HadNonBlankPaint() const {
+ return mHadNonBlankPaint;
+ }
+
+ void NotifyNonBlankPaint();
+
+ bool IsGlyph() const {
+ return mIsGlyph;
+ }
+
+ void SetIsGlyph(bool aValue) {
+ mIsGlyph = aValue;
+ }
+
+ bool UsesRootEMUnits() const {
+ return mUsesRootEMUnits;
+ }
+
+ void SetUsesRootEMUnits(bool aValue) {
+ mUsesRootEMUnits = aValue;
+ }
+
+ bool UsesExChUnits() const {
+ return mUsesExChUnits;
+ }
+
+ void SetUsesExChUnits(bool aValue) {
+ mUsesExChUnits = aValue;
+ }
+
+ bool UsesViewportUnits() const {
+ return mUsesViewportUnits;
+ }
+
+ void SetUsesViewportUnits(bool aValue) {
+ mUsesViewportUnits = aValue;
+ }
+
+ // true if there are OMTA transition updates for the current document which
+ // have been throttled, and therefore some style information may not be up
+ // to date
+ bool ExistThrottledUpdates() const {
+ return mExistThrottledUpdates;
+ }
+
+ void SetExistThrottledUpdates(bool aExistThrottledUpdates) {
+ mExistThrottledUpdates = aExistThrottledUpdates;
+ }
+
+ bool IsDeviceSizePageSize();
+
+ bool HasWarnedAboutPositionedTableParts() const {
+ return mHasWarnedAboutPositionedTableParts;
+ }
+
+ void SetHasWarnedAboutPositionedTableParts() {
+ mHasWarnedAboutPositionedTableParts = true;
+ }
+
+ bool HasWarnedAboutTooLargeDashedOrDottedRadius() const {
+ return mHasWarnedAboutTooLargeDashedOrDottedRadius;
+ }
+
+ void SetHasWarnedAboutTooLargeDashedOrDottedRadius() {
+ mHasWarnedAboutTooLargeDashedOrDottedRadius = true;
+ }
+
+protected:
+ friend class nsRunnableMethod<nsPresContext>;
+ void ThemeChangedInternal();
+ void SysColorChangedInternal();
+
+ // update device context's resolution from the widget
+ void UIResolutionChangedInternal();
+
+ // if aScale > 0.0, use it as resolution scale factor to the device context
+ // (otherwise get it from the widget)
+ void UIResolutionChangedInternalScale(double aScale);
+
+ // aData here is a pointer to a double that holds the CSS to device-pixel
+ // scale factor from the parent, which will be applied to the subdocument's
+ // device context instead of retrieving a scale from the widget.
+ static bool
+ UIResolutionChangedSubdocumentCallback(nsIDocument* aDocument, void* aData);
+
+ void SetImgAnimations(nsIContent *aParent, uint16_t aMode);
+ void SetSMILAnimations(nsIDocument *aDoc, uint16_t aNewMode,
+ uint16_t aOldMode);
+ void GetDocumentColorPreferences();
+
+ void PreferenceChanged(const char* aPrefName);
+ static void PrefChangedCallback(const char*, void*);
+
+ void UpdateAfterPreferencesChanged();
+ static void PrefChangedUpdateTimerCallback(nsITimer *aTimer, void *aClosure);
+
+ void GetUserPreferences();
+
+ /**
+ * Fetch the user's font preferences for the given aLanguage's
+ * langugage group.
+ */
+ const LangGroupFontPrefs* GetFontPrefsForLang(nsIAtom *aLanguage) const
+ {
+ nsIAtom* lang = aLanguage ? aLanguage : mLanguage.get();
+ return StaticPresData::Get()->GetFontPrefsForLangHelper(lang, &mLangGroupFontPrefs);
+ }
+
+ void UpdateCharSet(const nsCString& aCharSet);
+
+public:
+ void DoChangeCharSet(const nsCString& aCharSet);
+
+ /**
+ * Checks for MozAfterPaint listeners on the document
+ */
+ bool MayHavePaintEventListener();
+
+ /**
+ * Checks for MozAfterPaint listeners on the document and
+ * any subdocuments, except for subdocuments that are non-top-level
+ * content documents.
+ */
+ bool MayHavePaintEventListenerInSubDocument();
+
+#ifdef RESTYLE_LOGGING
+ // Controls for whether debug information about restyling in this
+ // document should be output.
+ bool RestyleLoggingEnabled() const { return mRestyleLoggingEnabled; }
+ void StartRestyleLogging() { mRestyleLoggingEnabled = true; }
+ void StopRestyleLogging() { mRestyleLoggingEnabled = false; }
+#endif
+
+ void InvalidatePaintedLayers();
+
+protected:
+ // May be called multiple times (unlink, destructor)
+ void Destroy();
+
+ void AppUnitsPerDevPixelChanged();
+
+ void HandleRebuildCounterStyles() {
+ mPostedFlushCounterStyles = false;
+ FlushCounterStyles();
+ }
+
+ bool HavePendingInputEvent();
+
+ // Can't be inline because we can't include nsStyleSet.h.
+ bool HasCachedStyleData();
+
+ // Creates a one-shot timer with the given aCallback & aDelay.
+ // Returns a refcounted pointer to the timer (or nullptr on failure).
+ already_AddRefed<nsITimer> CreateTimer(nsTimerCallbackFunc aCallback,
+ uint32_t aDelay);
+
+ // IMPORTANT: The ownership implicit in the following member variables
+ // has been explicitly checked. If you add any members to this class,
+ // please make the ownership explicit (pinkerton, scc).
+
+ nsPresContextType mType;
+ // the nsPresShell owns a strong reference to the nsPresContext, and is responsible
+ // for nulling this pointer before it is destroyed
+ nsIPresShell* MOZ_NON_OWNING_REF mShell; // [WEAK]
+ nsCOMPtr<nsIDocument> mDocument;
+ RefPtr<nsDeviceContext> mDeviceContext; // [STRONG] could be weak, but
+ // better safe than sorry.
+ // Cannot reintroduce cycles
+ // since there is no dependency
+ // from gfx back to layout.
+ RefPtr<mozilla::EventStateManager> mEventManager;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ RefPtr<mozilla::EffectCompositor> mEffectCompositor;
+ RefPtr<nsTransitionManager> mTransitionManager;
+ RefPtr<nsAnimationManager> mAnimationManager;
+ mozilla::RestyleManagerHandle::RefPtr mRestyleManager;
+ RefPtr<mozilla::CounterStyleManager> mCounterStyleManager;
+ nsIAtom* MOZ_UNSAFE_REF("always a static atom") mMedium; // initialized by subclass ctors
+ nsCOMPtr<nsIAtom> mMediaEmulated;
+
+ // This pointer is nulled out through SetLinkHandler() in the destructors of
+ // the classes which set it. (using SetLinkHandler() again).
+ nsILinkHandler* MOZ_NON_OWNING_REF mLinkHandler;
+
+ // Formerly mLangGroup; moving from charset-oriented langGroup to
+ // maintaining actual language settings everywhere (see bug 524107).
+ // This may in fact hold a langGroup such as x-western rather than
+ // a specific language, however (e.g, if it is inferred from the
+ // charset rather than explicitly specified as a lang attribute).
+ nsCOMPtr<nsIAtom> mLanguage;
+
+public:
+ // The following are public member variables so that we can use them
+ // with mozilla::AutoToggle or mozilla::AutoRestore.
+
+ // Should we disable font size inflation because we're inside of
+ // shrink-wrapping calculations on an inflation container?
+ bool mInflationDisabledForShrinkWrap;
+
+protected:
+
+ mozilla::WeakPtr<nsDocShell> mContainer;
+
+ // Base minimum font size, independent of the language-specific global preference. Defaults to 0
+ int32_t mBaseMinFontSize;
+ float mTextZoom; // Text zoom, defaults to 1.0
+ float mFullZoom; // Page zoom, defaults to 1.0
+ float mOverrideDPPX; // DPPX overrided, defaults to 0.0
+ gfxSize mLastFontInflationScreenSize;
+
+ int32_t mCurAppUnitsPerDevPixel;
+ int32_t mAutoQualityMinFontSizePixelsPref;
+
+ nsCOMPtr<nsITheme> mTheme;
+ nsCOMPtr<nsILanguageAtomService> mLangService;
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ nsCOMPtr<nsITimer> mPrefChangedTimer;
+
+ FramePropertyTable mPropertyTable;
+
+ nsInvalidateRequestList mInvalidateRequestsSinceLastPaint;
+ nsInvalidateRequestList mUndeliveredInvalidateRequestsBeforeLastPaint;
+
+ // text performance metrics
+ nsAutoPtr<gfxTextPerfMetrics> mTextPerf;
+
+ nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
+
+ nsRect mVisibleArea;
+ nsSize mPageSize;
+ float mPageScale;
+ float mPPScale;
+
+ nscolor mDefaultColor;
+ nscolor mBackgroundColor;
+
+ nscolor mLinkColor;
+ nscolor mActiveLinkColor;
+ nscolor mVisitedLinkColor;
+
+ nscolor mFocusBackgroundColor;
+ nscolor mFocusTextColor;
+
+ nscolor mBodyTextColor;
+
+ ScrollbarStyles mViewportStyleScrollbar;
+ uint8_t mFocusRingWidth;
+
+ bool mExistThrottledUpdates;
+
+ uint16_t mImageAnimationMode;
+ uint16_t mImageAnimationModePref;
+
+ // Most documents will only use one (or very few) language groups. Rather
+ // than have the overhead of a hash lookup, we simply look along what will
+ // typically be a very short (usually of length 1) linked list. There are 31
+ // language groups, so in the worst case scenario we'll need to traverse 31
+ // link items.
+ LangGroupFontPrefs mLangGroupFontPrefs;
+
+ nscoord mBorderWidthTable[3];
+
+ uint32_t mInterruptChecksToSkip;
+
+ // Counters for tests and tools that want to detect frame construction
+ // or reflow.
+ uint64_t mElementsRestyled;
+ uint64_t mFramesConstructed;
+ uint64_t mFramesReflowed;
+
+ mozilla::TimeStamp mReflowStartTime;
+
+ // last time we did a full style flush
+ mozilla::TimeStamp mLastStyleUpdateForAllAnimations;
+
+ unsigned mHasPendingInterrupt : 1;
+ unsigned mPendingInterruptFromTest : 1;
+ unsigned mInterruptsEnabled : 1;
+ unsigned mUseDocumentFonts : 1;
+ unsigned mUseDocumentColors : 1;
+ unsigned mUnderlineLinks : 1;
+ unsigned mSendAfterPaintToContent : 1;
+ unsigned mUseFocusColors : 1;
+ unsigned mFocusRingOnAnything : 1;
+ unsigned mFocusRingStyle : 1;
+ unsigned mDrawImageBackground : 1;
+ unsigned mDrawColorBackground : 1;
+ unsigned mNeverAnimate : 1;
+ unsigned mIsRenderingOnlySelection : 1;
+ unsigned mPaginated : 1;
+ unsigned mCanPaginatedScroll : 1;
+ unsigned mDoScaledTwips : 1;
+ unsigned mIsRootPaginatedDocument : 1;
+ unsigned mPrefBidiDirection : 1;
+ unsigned mPrefScrollbarSide : 2;
+ unsigned mPendingSysColorChanged : 1;
+ unsigned mPendingThemeChanged : 1;
+ unsigned mPendingUIResolutionChanged : 1;
+ unsigned mPendingMediaFeatureValuesChanged : 1;
+ unsigned mPrefChangePendingNeedsReflow : 1;
+ unsigned mIsEmulatingMedia : 1;
+ // True if the requests in mInvalidateRequestsSinceLastPaint cover the
+ // entire viewport
+ unsigned mAllInvalidated : 1;
+
+ // Are we currently drawing an SVG glyph?
+ unsigned mIsGlyph : 1;
+
+ // Does the associated document use root-em (rem) units?
+ unsigned mUsesRootEMUnits : 1;
+ // Does the associated document use ex or ch units?
+ unsigned mUsesExChUnits : 1;
+ // Does the associated document use viewport units (vw/vh/vmin/vmax)?
+ unsigned mUsesViewportUnits : 1;
+
+ // Has there been a change to the viewport's dimensions?
+ unsigned mPendingViewportChange : 1;
+
+ // Is the current mCounterStyleManager valid?
+ unsigned mCounterStylesDirty : 1;
+ // Do we currently have an event posted to call FlushCounterStyles?
+ unsigned mPostedFlushCounterStyles: 1;
+
+ // resize reflow is suppressed when the only change has been to zoom
+ // the document rather than to change the document's dimensions
+ unsigned mSuppressResizeReflow : 1;
+
+ unsigned mIsVisual : 1;
+
+ unsigned mFireAfterPaintEvents : 1;
+
+ unsigned mIsChrome : 1;
+ unsigned mIsChromeOriginImage : 1;
+
+ // Should we paint flash in this context? Do not use this variable directly.
+ // Use GetPaintFlashing() method instead.
+ mutable unsigned mPaintFlashing : 1;
+ mutable unsigned mPaintFlashingInitialized : 1;
+
+ unsigned mHasWarnedAboutPositionedTableParts : 1;
+
+ unsigned mHasWarnedAboutTooLargeDashedOrDottedRadius : 1;
+
+ // Have we added quirk.css to the style set?
+ unsigned mQuirkSheetAdded : 1;
+
+ // Is there a pref update to process once we have a container?
+ unsigned mNeedsPrefUpdate : 1;
+
+ // Has NotifyNonBlankPaint been called on this PresContext?
+ unsigned mHadNonBlankPaint : 1;
+
+#ifdef RESTYLE_LOGGING
+ // Should we output debug information about restyling for this document?
+ bool mRestyleLoggingEnabled;
+#endif
+
+#ifdef DEBUG
+ bool mInitialized;
+#endif
+
+
+protected:
+
+ virtual ~nsPresContext();
+
+ nscolor MakeColorPref(const nsString& aColor);
+
+ void LastRelease();
+
+#ifdef DEBUG
+private:
+ friend struct nsAutoLayoutPhase;
+ uint32_t mLayoutPhaseCount[eLayoutPhase_COUNT];
+public:
+ uint32_t LayoutPhaseCount(nsLayoutPhase aPhase) {
+ return mLayoutPhaseCount[aPhase];
+ }
+#endif
+
+};
+
+class nsRootPresContext final : public nsPresContext {
+public:
+ nsRootPresContext(nsIDocument* aDocument, nsPresContextType aType);
+ virtual ~nsRootPresContext();
+ virtual void Detach() override;
+
+ /**
+ * Ensure that NotifyDidPaintForSubtree is eventually called on this
+ * object after a timeout.
+ */
+ void EnsureEventualDidPaintEvent();
+
+ void CancelDidPaintTimer()
+ {
+ if (mNotifyDidPaintTimer) {
+ mNotifyDidPaintTimer->Cancel();
+ mNotifyDidPaintTimer = nullptr;
+ }
+ }
+
+ /**
+ * Registers a plugin to receive geometry updates (position and clip
+ * region) so it can update its widget.
+ * Callers must call UnregisterPluginForGeometryUpdates before
+ * the aPlugin frame is destroyed.
+ */
+ void RegisterPluginForGeometryUpdates(nsIContent* aPlugin);
+ /**
+ * Stops a plugin receiving geometry updates (position and clip
+ * region). If the plugin was not already registered, this does
+ * nothing.
+ */
+ void UnregisterPluginForGeometryUpdates(nsIContent* aPlugin);
+
+ bool NeedToComputePluginGeometryUpdates()
+ {
+ return mRegisteredPlugins.Count() > 0;
+ }
+ /**
+ * Compute geometry updates for each plugin given that aList is the display
+ * list for aFrame. The updates are not yet applied;
+ * ApplyPluginGeometryUpdates is responsible for that. In the meantime they
+ * are stored on each nsPluginFrame.
+ * This needs to be called even when aFrame is a popup, since although
+ * windowed plugins aren't allowed in popups, windowless plugins are
+ * and ComputePluginGeometryUpdates needs to be called for them.
+ */
+ void ComputePluginGeometryUpdates(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList);
+
+ /**
+ * Apply the stored plugin geometry updates. This should normally be called
+ * in DidPaint so the plugins are moved/clipped immediately after we've
+ * updated our window, so they look in sync with our window.
+ */
+ void ApplyPluginGeometryUpdates();
+
+ /**
+ * Transfer stored plugin geometry updates to the compositor. Called during
+ * reflow, data is shipped over with layer updates. e10s specific.
+ */
+ void CollectPluginGeometryUpdates(mozilla::layers::LayerManager* aLayerManager);
+
+ virtual bool IsRoot() override { return true; }
+
+ /**
+ * Increment DOM-modification generation counter to indicate that
+ * the DOM has changed in a way that might lead to style changes/
+ * reflows/frame creation and destruction.
+ */
+ void IncrementDOMGeneration() { mDOMGeneration++; }
+
+ /**
+ * Get the current DOM generation counter.
+ *
+ * See nsFrameManagerBase::GetGlobalGenerationNumber() for a
+ * global generation number.
+ */
+ uint32_t GetDOMGeneration() { return mDOMGeneration; }
+
+ /**
+ * Add a runnable that will get called before the next paint. They will get
+ * run eventually even if painting doesn't happen. They might run well before
+ * painting happens.
+ */
+ void AddWillPaintObserver(nsIRunnable* aRunnable);
+
+ /**
+ * Run all runnables that need to get called before the next paint.
+ */
+ void FlushWillPaintObservers();
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+protected:
+ /**
+ * Start a timer to ensure we eventually run ApplyPluginGeometryUpdates.
+ */
+ void InitApplyPluginGeometryTimer();
+ /**
+ * Cancel the timer that ensures we eventually run ApplyPluginGeometryUpdates.
+ */
+ void CancelApplyPluginGeometryTimer();
+
+ class RunWillPaintObservers : public mozilla::Runnable {
+ public:
+ explicit RunWillPaintObservers(nsRootPresContext* aPresContext) : mPresContext(aPresContext) {}
+ void Revoke() { mPresContext = nullptr; }
+ NS_IMETHOD Run() override
+ {
+ if (mPresContext) {
+ mPresContext->FlushWillPaintObservers();
+ }
+ return NS_OK;
+ }
+ // The lifetime of this reference is handled by an nsRevocableEventPtr
+ nsRootPresContext* MOZ_NON_OWNING_REF mPresContext;
+ };
+
+ friend class nsPresContext;
+
+ nsCOMPtr<nsITimer> mNotifyDidPaintTimer;
+ nsCOMPtr<nsITimer> mApplyPluginGeometryTimer;
+ nsTHashtable<nsRefPtrHashKey<nsIContent> > mRegisteredPlugins;
+ nsTArray<nsCOMPtr<nsIRunnable> > mWillPaintObservers;
+ nsRevocableEventPtr<RunWillPaintObservers> mWillPaintFallbackEvent;
+ uint32_t mDOMGeneration;
+};
+
+#ifdef MOZ_REFLOW_PERF
+
+#define DO_GLOBAL_REFLOW_COUNT(_name) \
+ aPresContext->CountReflows((_name), (nsIFrame*)this);
+#else
+#define DO_GLOBAL_REFLOW_COUNT(_name)
+#endif // MOZ_REFLOW_PERF
+
+#endif /* nsPresContext_h___ */
diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp
new file mode 100644
index 000000000..56ac370b9
--- /dev/null
+++ b/layout/base/nsPresShell.cpp
@@ -0,0 +1,11280 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 05/03/2000 IBM Corp. Observer events for reflow states
+ */
+
+/* a presentation of a document, part 2 */
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StyleBackendType.h"
+#include <algorithm>
+
+#ifdef XP_WIN
+#include "winuser.h"
+#endif
+
+#include "gfxPrefs.h"
+#include "gfxUserFontSet.h"
+#include "nsPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIContentIterator.h"
+#include "mozilla/dom/BeforeAfterKeyboardEvent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for Event::GetEventPopupControlState()
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/PointerEvent.h"
+#include "nsIDocument.h"
+#include "nsAnimationManager.h"
+#include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816)
+#include "nsFrame.h"
+#include "FrameLayerBuilder.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsCRTGlue.h"
+#include "prprf.h"
+#include "prinrval.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsContainerFrame.h"
+#include "nsISelection.h"
+#include "mozilla/dom/Selection.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMRange.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMElement.h"
+#include "nsRange.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsReadableUtils.h"
+#include "nsIPageSequenceFrame.h"
+#include "nsIPermissionManager.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsCaret.h"
+#include "AccessibleCaretEventHub.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsFrameManager.h"
+#include "nsXPCOM.h"
+#include "nsILayoutHistoryState.h"
+#include "nsILineIterator.h" // for ScrollContentIntoView
+#include "PLDHashTable.h"
+#include "mozilla/dom/BeforeAfterKeyboardEventBinding.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/dom/PointerEventBinding.h"
+#include "nsIObserverService.h"
+#include "nsDocShell.h" // for reflow observation
+#include "nsIBaseWindow.h"
+#include "nsError.h"
+#include "nsLayoutUtils.h"
+#include "nsViewportInfo.h"
+#include "nsCSSRendering.h"
+ // for |#ifdef DEBUG| code
+#include "prenv.h"
+#include "nsDisplayList.h"
+#include "nsRegion.h"
+#include "nsRenderingContext.h"
+#include "nsAutoLayoutPhase.h"
+#ifdef MOZ_REFLOW_PERF
+#include "nsFontMetrics.h"
+#endif
+#include "PositionedEventTargeting.h"
+
+#include "nsIReflowCallback.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsFocusManager.h"
+#include "nsIObjectFrame.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsStyleSheetService.h"
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "nsSMILAnimationController.h"
+#include "SVGContentUtils.h"
+#include "nsSVGEffects.h"
+#include "SVGFragmentIdentifier.h"
+#include "nsArenaMemoryStats.h"
+#include "nsFrameSelection.h"
+
+#include "mozilla/dom/Performance.h"
+#include "nsRefreshDriver.h"
+#include "nsDOMNavigationTiming.h"
+
+// Drag & Drop, Clipboard
+#include "nsIDocShellTreeItem.h"
+#include "nsIURI.h"
+#include "nsIScrollableFrame.h"
+#include "nsITimer.h"
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#include "mozilla/a11y/DocAccessible.h"
+#ifdef DEBUG
+#include "mozilla/a11y/Logging.h"
+#endif
+#endif
+
+// For style data reconstruction
+#include "nsStyleChangeList.h"
+#include "nsCSSFrameConstructor.h"
+#ifdef MOZ_XUL
+#include "nsMenuFrame.h"
+#include "nsTreeBodyFrame.h"
+#include "nsIBoxObject.h"
+#include "nsITreeBoxObject.h"
+#include "nsMenuPopupFrame.h"
+#include "nsITreeColumns.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULMenuListElement.h"
+
+#endif
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "GeckoProfiler.h"
+#include "gfxPlatform.h"
+#include "Layers.h"
+#include "LayerTreeInvalidation.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "nsCanvasFrame.h"
+#include "nsIImageLoadingContent.h"
+#include "nsImageFrame.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTransitionManager.h"
+#include "ChildIterator.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDragSession.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/gfx/2D.h"
+#include "nsSubDocumentFrame.h"
+#include "nsQueryObject.h"
+#include "nsLayoutStylesheetCache.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+#include "nsStyleSet.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/dom/ImageTracker.h"
+
+#ifdef ANDROID
+#include "nsIDocShellTreeOwner.h"
+#endif
+
+#ifdef MOZ_B2G
+#include "nsIHardwareKeyHandler.h"
+#endif
+
+#ifdef MOZ_TASK_TRACER
+#include "GeckoTaskTracer.h"
+using namespace mozilla::tasktracer;
+#endif
+
+#define ANCHOR_SCROLL_FLAGS \
+ (nsIPresShell::SCROLL_OVERFLOW_HIDDEN | nsIPresShell::SCROLL_NO_PARENT_FRAMES)
+
+ // define the scalfactor of drag and drop images
+ // relative to the max screen height/width
+#define RELATIVE_SCALEFACTOR 0.0925f
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+using namespace mozilla::layout;
+using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+
+CapturingContentInfo nsIPresShell::gCaptureInfo =
+ { false /* mAllowed */, false /* mPointerLock */, false /* mRetargetToElement */,
+ false /* mPreventDrag */ };
+nsIContent* nsIPresShell::gKeyDownTarget;
+
+// Keeps a map between pointerId and element that currently capturing pointer
+// with such pointerId. If pointerId is absent in this map then nobody is
+// capturing it. Additionally keep information about pending capturing content.
+static nsClassHashtable<nsUint32HashKey,
+ nsIPresShell::PointerCaptureInfo>* sPointerCaptureList;
+
+// Keeps information about pointers such as pointerId, activeState, pointerType,
+// primaryState
+static nsClassHashtable<nsUint32HashKey,
+ nsIPresShell::PointerInfo>* sActivePointersIds;
+
+// RangePaintInfo is used to paint ranges to offscreen buffers
+struct RangePaintInfo {
+ RefPtr<nsRange> mRange;
+ nsDisplayListBuilder mBuilder;
+ nsDisplayList mList;
+
+ // offset of builder's reference frame to the root frame
+ nsPoint mRootOffset;
+
+ RangePaintInfo(nsRange* aRange, nsIFrame* aFrame)
+ : mRange(aRange), mBuilder(aFrame, nsDisplayListBuilderMode::PAINTING, false)
+ {
+ MOZ_COUNT_CTOR(RangePaintInfo);
+ }
+
+ ~RangePaintInfo()
+ {
+ mList.DeleteAll();
+ MOZ_COUNT_DTOR(RangePaintInfo);
+ }
+};
+
+#undef NOISY
+
+// ----------------------------------------------------------------------
+
+#ifdef DEBUG
+// Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
+// more of the following flags (comma separated) for handy debug
+// output.
+static uint32_t gVerifyReflowFlags;
+
+struct VerifyReflowFlags {
+ const char* name;
+ uint32_t bit;
+};
+
+static const VerifyReflowFlags gFlags[] = {
+ { "verify", VERIFY_REFLOW_ON },
+ { "reflow", VERIFY_REFLOW_NOISY },
+ { "all", VERIFY_REFLOW_ALL },
+ { "list-commands", VERIFY_REFLOW_DUMP_COMMANDS },
+ { "noisy-commands", VERIFY_REFLOW_NOISY_RC },
+ { "really-noisy-commands", VERIFY_REFLOW_REALLY_NOISY_RC },
+ { "resize", VERIFY_REFLOW_DURING_RESIZE_REFLOW },
+};
+
+#define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
+
+static void
+ShowVerifyReflowFlags()
+{
+ printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
+ const VerifyReflowFlags* flag = gFlags;
+ const VerifyReflowFlags* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
+ while (flag < limit) {
+ printf(" %s\n", flag->name);
+ ++flag;
+ }
+ printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
+ printf("names (no whitespace)\n");
+}
+#endif
+
+//========================================================================
+//========================================================================
+//========================================================================
+#ifdef MOZ_REFLOW_PERF
+class ReflowCountMgr;
+
+static const char kGrandTotalsStr[] = "Grand Totals";
+
+// Counting Class
+class ReflowCounter {
+public:
+ explicit ReflowCounter(ReflowCountMgr * aMgr = nullptr);
+ ~ReflowCounter();
+
+ void ClearTotals();
+ void DisplayTotals(const char * aStr);
+ void DisplayDiffTotals(const char * aStr);
+ void DisplayHTMLTotals(const char * aStr);
+
+ void Add() { mTotal++; }
+ void Add(uint32_t aTotal) { mTotal += aTotal; }
+
+ void CalcDiffInTotals();
+ void SetTotalsCache();
+
+ void SetMgr(ReflowCountMgr * aMgr) { mMgr = aMgr; }
+
+ uint32_t GetTotal() { return mTotal; }
+
+protected:
+ void DisplayTotals(uint32_t aTotal, const char * aTitle);
+ void DisplayHTMLTotals(uint32_t aTotal, const char * aTitle);
+
+ uint32_t mTotal;
+ uint32_t mCacheTotal;
+
+ ReflowCountMgr * mMgr; // weak reference (don't delete)
+};
+
+// Counting Class
+class IndiReflowCounter {
+public:
+ explicit IndiReflowCounter(ReflowCountMgr * aMgr = nullptr)
+ : mFrame(nullptr),
+ mCount(0),
+ mMgr(aMgr),
+ mCounter(aMgr),
+ mHasBeenOutput(false)
+ {}
+ virtual ~IndiReflowCounter() {}
+
+ nsAutoString mName;
+ nsIFrame * mFrame; // weak reference (don't delete)
+ int32_t mCount;
+
+ ReflowCountMgr * mMgr; // weak reference (don't delete)
+
+ ReflowCounter mCounter;
+ bool mHasBeenOutput;
+
+};
+
+//--------------------
+// Manager Class
+//--------------------
+class ReflowCountMgr {
+public:
+ ReflowCountMgr();
+ virtual ~ReflowCountMgr();
+
+ void ClearTotals();
+ void ClearGrandTotals();
+ void DisplayTotals(const char * aStr);
+ void DisplayHTMLTotals(const char * aStr);
+ void DisplayDiffsInTotals();
+
+ void Add(const char * aName, nsIFrame * aFrame);
+ ReflowCounter * LookUp(const char * aName);
+
+ void PaintCount(const char *aName, nsRenderingContext* aRenderingContext,
+ nsPresContext *aPresContext, nsIFrame *aFrame,
+ const nsPoint &aOffset, uint32_t aColor);
+
+ FILE * GetOutFile() { return mFD; }
+
+ PLHashTable * GetIndiFrameHT() { return mIndiFrameCounts; }
+
+ void SetPresContext(nsPresContext * aPresContext) { mPresContext = aPresContext; } // weak reference
+ void SetPresShell(nsIPresShell* aPresShell) { mPresShell= aPresShell; } // weak reference
+
+ void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
+ void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
+ void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }
+
+ bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }
+
+protected:
+ void DisplayTotals(uint32_t aTotal, uint32_t * aDupArray, char * aTitle);
+ void DisplayHTMLTotals(uint32_t aTotal, uint32_t * aDupArray, char * aTitle);
+
+ static int RemoveItems(PLHashEntry *he, int i, void *arg);
+ static int RemoveIndiItems(PLHashEntry *he, int i, void *arg);
+ void CleanUp();
+
+ // stdout Output Methods
+ static int DoSingleTotal(PLHashEntry *he, int i, void *arg);
+ static int DoSingleIndi(PLHashEntry *he, int i, void *arg);
+
+ void DoGrandTotals();
+ void DoIndiTotalsTree();
+
+ // HTML Output Methods
+ static int DoSingleHTMLTotal(PLHashEntry *he, int i, void *arg);
+ void DoGrandHTMLTotals();
+
+ // Zero Out the Totals
+ static int DoClearTotals(PLHashEntry *he, int i, void *arg);
+
+ // Displays the Diff Totals
+ static int DoDisplayDiffTotals(PLHashEntry *he, int i, void *arg);
+
+ PLHashTable * mCounts;
+ PLHashTable * mIndiFrameCounts;
+ FILE * mFD;
+
+ bool mDumpFrameCounts;
+ bool mDumpFrameByFrameCounts;
+ bool mPaintFrameByFrameCounts;
+
+ bool mCycledOnce;
+
+ // Root Frame for Individual Tracking
+ nsPresContext * mPresContext;
+ nsIPresShell* mPresShell;
+
+ // ReflowCountMgr gReflowCountMgr;
+};
+#endif
+//========================================================================
+
+// comment out to hide caret
+#define SHOW_CARET
+
+// The upper bound on the amount of time to spend reflowing, in
+// microseconds. When this bound is exceeded and reflow commands are
+// still queued up, a reflow event is posted. The idea is for reflow
+// to not hog the processor beyond the time specifed in
+// gMaxRCProcessingTime. This data member is initialized from the
+// layout.reflow.timeslice pref.
+#define NS_MAX_REFLOW_TIME 1000000
+static int32_t gMaxRCProcessingTime = -1;
+
+struct nsCallbackEventRequest
+{
+ nsIReflowCallback* callback;
+ nsCallbackEventRequest* next;
+};
+
+// ----------------------------------------------------------------------------
+#define ASSERT_REFLOW_SCHEDULED_STATE() \
+ NS_ASSERTION(mReflowScheduled == \
+ GetPresContext()->RefreshDriver()-> \
+ IsLayoutFlushObserver(this), "Unexpected state")
+
+class nsAutoCauseReflowNotifier
+{
+public:
+ explicit nsAutoCauseReflowNotifier(PresShell* aShell)
+ : mShell(aShell)
+ {
+ mShell->WillCauseReflow();
+ }
+ ~nsAutoCauseReflowNotifier()
+ {
+ // This check should not be needed. Currently the only place that seem
+ // to need it is the code that deals with bug 337586.
+ if (!mShell->mHaveShutDown) {
+ mShell->DidCauseReflow();
+ }
+ else {
+ nsContentUtils::RemoveScriptBlocker();
+ }
+ }
+
+ PresShell* mShell;
+};
+
+class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback
+{
+public:
+ explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
+
+ virtual void HandleEvent(EventChainPostVisitor& aVisitor) override
+ {
+ if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
+ if (aVisitor.mEvent->mMessage == eMouseDown ||
+ aVisitor.mEvent->mMessage == eMouseUp) {
+ // Mouse-up and mouse-down events call nsFrame::HandlePress/Release
+ // which call GetContentOffsetsFromPoint which requires up-to-date layout.
+ // Bring layout up-to-date now so that GetCurrentEventFrame() below
+ // will return a real frame and we don't have to worry about
+ // destroying it by flushing later.
+ mPresShell->FlushPendingNotifications(Flush_Layout);
+ } else if (aVisitor.mEvent->mMessage == eWheel &&
+ aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ nsIFrame* frame = mPresShell->GetCurrentEventFrame();
+ if (frame) {
+ // chrome (including addons) should be able to know if content
+ // handles both D3E "wheel" event and legacy mouse scroll events.
+ // We should dispatch legacy mouse events before dispatching the
+ // "wheel" event into system group.
+ RefPtr<EventStateManager> esm =
+ aVisitor.mPresContext->EventStateManager();
+ esm->DispatchLegacyMouseScrollEvents(frame,
+ aVisitor.mEvent->AsWheelEvent(),
+ &aVisitor.mEventStatus);
+ }
+ }
+ nsIFrame* frame = mPresShell->GetCurrentEventFrame();
+ if (!frame &&
+ (aVisitor.mEvent->mMessage == eMouseUp ||
+ aVisitor.mEvent->mMessage == eTouchEnd)) {
+ // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
+ // that capturing is released.
+ frame = mPresShell->GetRootFrame();
+ }
+ if (frame) {
+ frame->HandleEvent(aVisitor.mPresContext,
+ aVisitor.mEvent->AsGUIEvent(),
+ &aVisitor.mEventStatus);
+ }
+ }
+ }
+
+ RefPtr<PresShell> mPresShell;
+};
+
+class nsBeforeFirstPaintDispatcher : public Runnable
+{
+public:
+ explicit nsBeforeFirstPaintDispatcher(nsIDocument* aDocument)
+ : mDocument(aDocument) {}
+
+ // Fires the "before-first-paint" event so that interested parties (right now, the
+ // mobile browser) are aware of it.
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(mDocument, "before-first-paint",
+ nullptr);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+bool PresShell::sDisableNonTestMouseEvents = false;
+
+mozilla::LazyLogModule PresShell::gLog("PresShell");
+
+#ifdef DEBUG
+static void
+VerifyStyleTree(nsPresContext* aPresContext, nsFrameManager* aFrameManager)
+{
+ if (nsFrame::GetVerifyStyleTreeEnable()) {
+ if (aPresContext->RestyleManager()->IsServo()) {
+ NS_ERROR("stylo: cannot verify style tree with a ServoRestyleManager");
+ return;
+ }
+ nsIFrame* rootFrame = aFrameManager->GetRootFrame();
+ aPresContext->RestyleManager()->AsGecko()->DebugVerifyStyleTree(rootFrame);
+ }
+}
+#define VERIFY_STYLE_TREE ::VerifyStyleTree(mPresContext, mFrameConstructor)
+#else
+#define VERIFY_STYLE_TREE
+#endif
+
+static bool gVerifyReflowEnabled;
+
+bool
+nsIPresShell::GetVerifyReflowEnable()
+{
+#ifdef DEBUG
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
+ if (flags) {
+ bool error = false;
+
+ for (;;) {
+ char* comma = PL_strchr(flags, ',');
+ if (comma)
+ *comma = '\0';
+
+ bool found = false;
+ const VerifyReflowFlags* flag = gFlags;
+ const VerifyReflowFlags* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
+ while (flag < limit) {
+ if (PL_strcasecmp(flag->name, flags) == 0) {
+ gVerifyReflowFlags |= flag->bit;
+ found = true;
+ break;
+ }
+ ++flag;
+ }
+
+ if (! found)
+ error = true;
+
+ if (! comma)
+ break;
+
+ *comma = ',';
+ flags = comma + 1;
+ }
+
+ if (error)
+ ShowVerifyReflowFlags();
+ }
+
+ if (VERIFY_REFLOW_ON & gVerifyReflowFlags) {
+ gVerifyReflowEnabled = true;
+
+ printf("Note: verifyreflow is enabled");
+ if (VERIFY_REFLOW_NOISY & gVerifyReflowFlags) {
+ printf(" (noisy)");
+ }
+ if (VERIFY_REFLOW_ALL & gVerifyReflowFlags) {
+ printf(" (all)");
+ }
+ if (VERIFY_REFLOW_DUMP_COMMANDS & gVerifyReflowFlags) {
+ printf(" (show reflow commands)");
+ }
+ if (VERIFY_REFLOW_NOISY_RC & gVerifyReflowFlags) {
+ printf(" (noisy reflow commands)");
+ if (VERIFY_REFLOW_REALLY_NOISY_RC & gVerifyReflowFlags) {
+ printf(" (REALLY noisy reflow commands)");
+ }
+ }
+ printf("\n");
+ }
+ }
+#endif
+ return gVerifyReflowEnabled;
+}
+
+void
+PresShell::AddInvalidateHiddenPresShellObserver(nsRefreshDriver *aDriver)
+{
+ if (!mHiddenInvalidationObserverRefreshDriver && !mIsDestroying && !mHaveShutDown) {
+ aDriver->AddPresShellToInvalidateIfHidden(this);
+ mHiddenInvalidationObserverRefreshDriver = aDriver;
+ }
+}
+
+void
+nsIPresShell::InvalidatePresShellIfHidden()
+{
+ if (!IsVisible() && mPresContext) {
+ mPresContext->NotifyInvalidation(0);
+ }
+ mHiddenInvalidationObserverRefreshDriver = nullptr;
+}
+
+void
+nsIPresShell::CancelInvalidatePresShellIfHidden()
+{
+ if (mHiddenInvalidationObserverRefreshDriver) {
+ mHiddenInvalidationObserverRefreshDriver->RemovePresShellToInvalidateIfHidden(this);
+ mHiddenInvalidationObserverRefreshDriver = nullptr;
+ }
+}
+
+void
+nsIPresShell::SetVerifyReflowEnable(bool aEnabled)
+{
+ gVerifyReflowEnabled = aEnabled;
+}
+
+/* virtual */ void
+nsIPresShell::AddWeakFrameExternal(nsWeakFrame* aWeakFrame)
+{
+ AddWeakFrameInternal(aWeakFrame);
+}
+
+void
+nsIPresShell::AddWeakFrameInternal(nsWeakFrame* aWeakFrame)
+{
+ if (aWeakFrame->GetFrame()) {
+ aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
+ }
+ aWeakFrame->SetPreviousWeakFrame(mWeakFrames);
+ mWeakFrames = aWeakFrame;
+}
+
+/* virtual */ void
+nsIPresShell::RemoveWeakFrameExternal(nsWeakFrame* aWeakFrame)
+{
+ RemoveWeakFrameInternal(aWeakFrame);
+}
+
+void
+nsIPresShell::RemoveWeakFrameInternal(nsWeakFrame* aWeakFrame)
+{
+ if (mWeakFrames == aWeakFrame) {
+ mWeakFrames = aWeakFrame->GetPreviousWeakFrame();
+ return;
+ }
+ nsWeakFrame* nextWeak = mWeakFrames;
+ while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
+ nextWeak = nextWeak->GetPreviousWeakFrame();
+ }
+ if (nextWeak) {
+ nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
+ }
+}
+
+already_AddRefed<nsFrameSelection>
+nsIPresShell::FrameSelection()
+{
+ RefPtr<nsFrameSelection> ret = mSelection;
+ return ret.forget();
+}
+
+//----------------------------------------------------------------------
+
+static bool sSynthMouseMove = true;
+static uint32_t sNextPresShellId;
+static bool sPointerEventEnabled = true;
+static bool sPointerEventImplicitCapture = false;
+static bool sAccessibleCaretEnabled = false;
+static bool sAccessibleCaretOnTouch = false;
+static bool sBeforeAfterKeyboardEventEnabled = false;
+
+/* static */ bool
+PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell)
+{
+ static bool initialized = false;
+ if (!initialized) {
+ Preferences::AddBoolVarCache(&sAccessibleCaretEnabled, "layout.accessiblecaret.enabled");
+ Preferences::AddBoolVarCache(&sAccessibleCaretOnTouch, "layout.accessiblecaret.enabled_on_touch");
+ initialized = true;
+ }
+ // If the pref forces it on, then enable it.
+ if (sAccessibleCaretEnabled) {
+ return true;
+ }
+ // If the touch pref is on, and touch events are enabled (this depends
+ // on the specific device running), then enable it.
+ if (sAccessibleCaretOnTouch && dom::TouchEvent::PrefEnabled(aDocShell)) {
+ return true;
+ }
+ // Otherwise, disabled.
+ return false;
+}
+
+/* static */ bool
+PresShell::BeforeAfterKeyboardEventEnabled()
+{
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ Preferences::AddBoolVarCache(&sBeforeAfterKeyboardEventEnabled,
+ "dom.beforeAfterKeyboardEvent.enabled");
+ sInitialized = true;
+ }
+ return sBeforeAfterKeyboardEventEnabled;
+}
+
+/* static */ bool
+PresShell::IsTargetIframe(nsINode* aTarget)
+{
+ return aTarget && aTarget->IsHTMLElement(nsGkAtoms::iframe);
+}
+
+PresShell::PresShell()
+ : mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)
+{
+#ifdef MOZ_REFLOW_PERF
+ mReflowCountMgr = new ReflowCountMgr();
+ mReflowCountMgr->SetPresContext(mPresContext);
+ mReflowCountMgr->SetPresShell(this);
+#endif
+ mLoadBegin = TimeStamp::Now();
+
+ mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
+ mIsThemeSupportDisabled = false;
+ mIsActive = true;
+ // FIXME/bug 735029: find a better solution to this problem
+ mIsFirstPaint = true;
+ mPresShellId = sNextPresShellId++;
+ mFrozen = false;
+ mRenderFlags = 0;
+
+ mScrollPositionClampingScrollPortSizeSet = false;
+
+ static bool addedSynthMouseMove = false;
+ if (!addedSynthMouseMove) {
+ Preferences::AddBoolVarCache(&sSynthMouseMove,
+ "layout.reflow.synthMouseMove", true);
+ addedSynthMouseMove = true;
+ }
+ static bool addedPointerEventEnabled = false;
+ if (!addedPointerEventEnabled) {
+ Preferences::AddBoolVarCache(&sPointerEventEnabled,
+ "dom.w3c_pointer_events.enabled", true);
+ addedPointerEventEnabled = true;
+ }
+ static bool addedPointerEventImplicitCapture = false;
+ if (!addedPointerEventImplicitCapture) {
+ Preferences::AddBoolVarCache(&sPointerEventImplicitCapture,
+ "dom.w3c_pointer_events.implicit_capture",
+ true);
+ addedPointerEventImplicitCapture = true;
+ }
+ mPaintingIsFrozen = false;
+ mHasCSSBackgroundColor = true;
+ mIsLastChromeOnlyEscapeKeyConsumed = false;
+ mHasReceivedPaintMessage = false;
+}
+
+NS_IMPL_ISUPPORTS(PresShell, nsIPresShell, nsIDocumentObserver,
+ nsISelectionController,
+ nsISelectionDisplay, nsIObserver, nsISupportsWeakReference,
+ nsIMutationObserver)
+
+PresShell::~PresShell()
+{
+ if (!mHaveShutDown) {
+ NS_NOTREACHED("Someone did not call nsIPresShell::destroy");
+ Destroy();
+ }
+
+ NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
+ "Huh, event content left on the stack in pres shell dtor!");
+ NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
+ mLastCallbackEventRequest == nullptr,
+ "post-reflow queues not empty. This means we're leaking");
+
+ // Verify that if painting was frozen, but we're being removed from the tree,
+ // that we now re-enable painting on our refresh driver, since it may need to
+ // be re-used by another presentation.
+ if (mPaintingIsFrozen) {
+ mPresContext->RefreshDriver()->Thaw();
+ }
+
+ MOZ_ASSERT(mAllocatedPointers.IsEmpty(), "Some pres arena objects were not freed");
+
+ mStyleSet->Delete();
+ delete mFrameConstructor;
+
+ mCurrentEventContent = nullptr;
+}
+
+/**
+ * Initialize the presentation shell. Create view manager and style
+ * manager.
+ * Note this can't be merged into our constructor because caret initialization
+ * calls AddRef() on us.
+ */
+void
+PresShell::Init(nsIDocument* aDocument,
+ nsPresContext* aPresContext,
+ nsViewManager* aViewManager,
+ StyleSetHandle aStyleSet)
+{
+ NS_PRECONDITION(aDocument, "null ptr");
+ NS_PRECONDITION(aPresContext, "null ptr");
+ NS_PRECONDITION(aViewManager, "null ptr");
+ NS_PRECONDITION(!mDocument, "already initialized");
+
+ if (!aDocument || !aPresContext || !aViewManager || mDocument) {
+ return;
+ }
+
+ mDocument = aDocument;
+ mViewManager = aViewManager;
+
+ // Create our frame constructor.
+ mFrameConstructor = new nsCSSFrameConstructor(mDocument, this);
+
+ mFrameManager = mFrameConstructor;
+
+ // The document viewer owns both view manager and pres shell.
+ mViewManager->SetPresShell(this);
+
+ // Bind the context to the presentation shell.
+ mPresContext = aPresContext;
+ StyleBackendType backend = aStyleSet->IsServo() ? StyleBackendType::Servo
+ : StyleBackendType::Gecko;
+ aPresContext->AttachShell(this, backend);
+
+ // Now we can initialize the style set. Make sure to set the member before
+ // calling Init, since various subroutines need to find the style set off
+ // the PresContext during initialization.
+ mStyleSet = aStyleSet;
+ mStyleSet->Init(aPresContext);
+
+ // Notify our prescontext that it now has a compatibility mode. Note that
+ // this MUST happen after we set up our style set but before we create any
+ // frames.
+ mPresContext->CompatibilityModeChanged();
+
+ // Add the preference style sheet.
+ UpdatePreferenceStyles();
+
+ if (AccessibleCaretEnabled(mDocument->GetDocShell())) {
+ // Need to happen before nsFrameSelection has been set up.
+ mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
+ }
+
+ mSelection = new nsFrameSelection();
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->Init(this, nullptr);
+
+ // Important: this has to happen after the selection has been set up
+#ifdef SHOW_CARET
+ // make the caret
+ mCaret = new nsCaret();
+ mCaret->Init(this);
+ mOriginalCaret = mCaret;
+
+ //SetCaretEnabled(true); // make it show in browser windows
+#endif
+ //set up selection to be displayed in document
+ // Don't enable selection for print media
+ nsPresContext::nsPresContextType type = aPresContext->Type();
+ if (type != nsPresContext::eContext_PrintPreview &&
+ type != nsPresContext::eContext_Print)
+ SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
+
+ if (gMaxRCProcessingTime == -1) {
+ gMaxRCProcessingTime =
+ Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
+ }
+
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "agent-sheet-added", false);
+ os->AddObserver(this, "user-sheet-added", false);
+ os->AddObserver(this, "author-sheet-added", false);
+ os->AddObserver(this, "agent-sheet-removed", false);
+ os->AddObserver(this, "user-sheet-removed", false);
+ os->AddObserver(this, "author-sheet-removed", false);
+#ifdef MOZ_XUL
+ os->AddObserver(this, "chrome-flush-skin-caches", false);
+#endif
+ os->AddObserver(this, "memory-pressure", false);
+ }
+ }
+
+#ifdef MOZ_REFLOW_PERF
+ if (mReflowCountMgr) {
+ bool paintFrameCounts =
+ Preferences::GetBool("layout.reflow.showframecounts");
+
+ bool dumpFrameCounts =
+ Preferences::GetBool("layout.reflow.dumpframecounts");
+
+ bool dumpFrameByFrameCounts =
+ Preferences::GetBool("layout.reflow.dumpframebyframecounts");
+
+ mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
+ mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
+ mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
+ }
+#endif
+
+ if (mDocument->HasAnimationController()) {
+ nsSMILAnimationController* animCtrl = mDocument->GetAnimationController();
+ animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
+ }
+
+ for (DocumentTimeline* timeline : mDocument->Timelines()) {
+ timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
+ }
+
+ // Get our activeness from the docShell.
+ QueryIsActive();
+
+ // Setup our font inflation preferences.
+ SetupFontInflation();
+
+ mTouchManager.Init(this, mDocument);
+
+ if (mPresContext->IsRootContentDocument()) {
+ mZoomConstraintsClient = new ZoomConstraintsClient();
+ mZoomConstraintsClient->Init(this, mDocument);
+ if (gfxPrefs::MetaViewportEnabled() || gfxPrefs::APZAllowZooming()) {
+ mMobileViewportManager = new MobileViewportManager(this, mDocument);
+ }
+ }
+}
+
+enum TextPerfLogType {
+ eLog_reflow,
+ eLog_loaddone,
+ eLog_totals
+};
+
+static void
+LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
+ PresShell* aPresShell,
+ const gfxTextPerfMetrics::TextCounts& aCounts,
+ float aTime, TextPerfLogType aLogType, const char* aURL)
+{
+ LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);
+
+ // ignore XUL contexts unless at debug level
+ mozilla::LogLevel logLevel = LogLevel::Warning;
+ if (aCounts.numContentTextRuns == 0) {
+ logLevel = LogLevel::Debug;
+ }
+
+ if (!MOZ_LOG_TEST(tpLog, logLevel)) {
+ return;
+ }
+
+ char prefix[256];
+
+ switch (aLogType) {
+ case eLog_reflow:
+ SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell, aTime);
+ break;
+ case eLog_loaddone:
+ SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f", aPresShell, aTime);
+ break;
+ default:
+ MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
+ SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
+ }
+
+ double hitRatio = 0.0;
+ uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
+ if (lookups) {
+ hitRatio = double(aCounts.wordCacheHit) / double(lookups);
+ }
+
+ if (aLogType == eLog_loaddone) {
+ MOZ_LOG(tpLog, logLevel,
+ ("%s reflow: %d chars: %d "
+ "[%s] "
+ "content-textruns: %d chrome-textruns: %d "
+ "max-textrun-len: %d "
+ "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
+ "word-cache-space: %d word-cache-long: %d "
+ "pref-fallbacks: %d system-fallbacks: %d "
+ "textruns-const: %d textruns-destr: %d "
+ "generic-lookups: %d "
+ "cumulative-textruns-destr: %d\n",
+ prefix, aTextPerf->reflowCount, aCounts.numChars,
+ (aURL ? aURL : ""),
+ aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
+ aCounts.maxTextRunLen,
+ lookups, hitRatio,
+ aCounts.wordCacheSpaceRules, aCounts.wordCacheLong,
+ aCounts.fallbackPrefs, aCounts.fallbackSystem,
+ aCounts.textrunConst, aCounts.textrunDestr,
+ aCounts.genericLookups,
+ aTextPerf->cumulative.textrunDestr));
+ } else {
+ MOZ_LOG(tpLog, logLevel,
+ ("%s reflow: %d chars: %d "
+ "content-textruns: %d chrome-textruns: %d "
+ "max-textrun-len: %d "
+ "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
+ "word-cache-space: %d word-cache-long: %d "
+ "pref-fallbacks: %d system-fallbacks: %d "
+ "textruns-const: %d textruns-destr: %d "
+ "generic-lookups: %d "
+ "cumulative-textruns-destr: %d\n",
+ prefix, aTextPerf->reflowCount, aCounts.numChars,
+ aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
+ aCounts.maxTextRunLen,
+ lookups, hitRatio,
+ aCounts.wordCacheSpaceRules, aCounts.wordCacheLong,
+ aCounts.fallbackPrefs, aCounts.fallbackSystem,
+ aCounts.textrunConst, aCounts.textrunDestr,
+ aCounts.genericLookups,
+ aTextPerf->cumulative.textrunDestr));
+ }
+}
+
+void
+PresShell::Destroy()
+{
+ // Do not add code before this line please!
+ if (mHaveShutDown) {
+ return;
+ }
+
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "destroy called on presshell while scripts not blocked");
+
+ // dump out cumulative text perf metrics
+ gfxTextPerfMetrics* tp;
+ if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
+ tp->Accumulate();
+ if (tp->cumulative.numChars > 0) {
+ LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
+ }
+ }
+ if (mPresContext) {
+ const bool mayFlushUserFontSet = false;
+ gfxUserFontSet* fs = mPresContext->GetUserFontSet(mayFlushUserFontSet);
+ if (fs) {
+ uint32_t fontCount;
+ uint64_t fontSize;
+ fs->GetLoadStatistics(fontCount, fontSize);
+ Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
+ uint32_t(fontSize/1024));
+ } else {
+ Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
+ }
+ }
+
+#ifdef MOZ_REFLOW_PERF
+ DumpReflows();
+ if (mReflowCountMgr) {
+ delete mReflowCountMgr;
+ mReflowCountMgr = nullptr;
+ }
+#endif
+
+ if (mZoomConstraintsClient) {
+ mZoomConstraintsClient->Destroy();
+ mZoomConstraintsClient = nullptr;
+ }
+ if (mMobileViewportManager) {
+ mMobileViewportManager->Destroy();
+ mMobileViewportManager = nullptr;
+ }
+
+#ifdef ACCESSIBILITY
+ if (mDocAccessible) {
+#ifdef DEBUG
+ if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
+ a11y::logging::DocDestroy("presshell destroyed", mDocument);
+#endif
+
+ mDocAccessible->Shutdown();
+ mDocAccessible = nullptr;
+ }
+#endif // ACCESSIBILITY
+
+ MaybeReleaseCapturingContent();
+
+ if (gKeyDownTarget && gKeyDownTarget->OwnerDoc() == mDocument) {
+ NS_RELEASE(gKeyDownTarget);
+ }
+
+ if (mContentToScrollTo) {
+ mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ }
+
+ if (mPresContext) {
+ // We need to notify the destroying the nsPresContext to ESM for
+ // suppressing to use from ESM.
+ mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
+ }
+
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, "agent-sheet-added");
+ os->RemoveObserver(this, "user-sheet-added");
+ os->RemoveObserver(this, "author-sheet-added");
+ os->RemoveObserver(this, "agent-sheet-removed");
+ os->RemoveObserver(this, "user-sheet-removed");
+ os->RemoveObserver(this, "author-sheet-removed");
+#ifdef MOZ_XUL
+ os->RemoveObserver(this, "chrome-flush-skin-caches");
+#endif
+ os->RemoveObserver(this, "memory-pressure");
+ }
+ }
+
+ // If our paint suppression timer is still active, kill it.
+ if (mPaintSuppressionTimer) {
+ mPaintSuppressionTimer->Cancel();
+ mPaintSuppressionTimer = nullptr;
+ }
+
+ // Same for our reflow continuation timer
+ if (mReflowContinueTimer) {
+ mReflowContinueTimer->Cancel();
+ mReflowContinueTimer = nullptr;
+ }
+
+ if (mDelayedPaintTimer) {
+ mDelayedPaintTimer->Cancel();
+ mDelayedPaintTimer = nullptr;
+ }
+
+ mSynthMouseMoveEvent.Revoke();
+
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES));
+
+ if (mCaret) {
+ mCaret->Terminate();
+ mCaret = nullptr;
+ }
+
+ if (mSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->DisconnectFromPresShell();
+ }
+
+ if (mAccessibleCaretEventHub) {
+ mAccessibleCaretEventHub->Terminate();
+ mAccessibleCaretEventHub = nullptr;
+ }
+
+ // release our pref style sheet, if we have one still
+ RemovePreferenceStyles();
+
+ mIsDestroying = true;
+
+ // We can't release all the event content in
+ // mCurrentEventContentStack here since there might be code on the
+ // stack that will release the event content too. Double release
+ // bad!
+
+ // The frames will be torn down, so remove them from the current
+ // event frame stack (since they'd be dangling references if we'd
+ // leave them in) and null out the mCurrentEventFrame pointer as
+ // well.
+
+ mCurrentEventFrame = nullptr;
+
+ int32_t i, count = mCurrentEventFrameStack.Length();
+ for (i = 0; i < count; i++) {
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+
+ mFramesToDirty.Clear();
+
+ if (mViewManager) {
+ // Clear the view manager's weak pointer back to |this| in case it
+ // was leaked.
+ mViewManager->SetPresShell(nullptr);
+ mViewManager = nullptr;
+ }
+
+ // mFrameArena will be destroyed soon. Clear out any ArenaRefPtrs
+ // pointing to objects in the arena now. This is done:
+ //
+ // (a) before mFrameArena's destructor runs so that our
+ // mAllocatedPointers becomes empty and doesn't trip the assertion
+ // in ~PresShell,
+ // (b) before the mPresContext->DetachShell() below, so
+ // that when we clear the ArenaRefPtrs they'll still be able to
+ // get back to this PresShell to deregister themselves (e.g. note
+ // how nsStyleContext::Arena returns the PresShell got from its
+ // rule node's nsPresContext, which would return null if we'd already
+ // called mPresContext->DetachShell()), and
+ // (c) before the mStyleSet->BeginShutdown() call just below, so that
+ // the nsStyleContexts don't complain they're being destroyed later
+ // than the rule tree is.
+ mFrameArena.ClearArenaRefPtrs();
+
+ mStyleSet->BeginShutdown();
+ nsRefreshDriver* rd = GetPresContext()->RefreshDriver();
+
+ // This shell must be removed from the document before the frame
+ // hierarchy is torn down to avoid finding deleted frames through
+ // this presshell while the frames are being torn down
+ if (mDocument) {
+ NS_ASSERTION(mDocument->GetShell() == this, "Wrong shell?");
+ mDocument->DeleteShell();
+
+ if (mDocument->HasAnimationController()) {
+ mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
+ }
+ for (DocumentTimeline* timeline : mDocument->Timelines()) {
+ timeline->NotifyRefreshDriverDestroying(rd);
+ }
+ }
+
+ if (mPresContext) {
+ mPresContext->AnimationManager()->ClearEventQueue();
+ mPresContext->TransitionManager()->ClearEventQueue();
+ }
+
+ // Revoke any pending events. We need to do this and cancel pending reflows
+ // before we destroy the frame manager, since apparently frame destruction
+ // sometimes spins the event queue when plug-ins are involved(!).
+ rd->RemoveLayoutFlushObserver(this);
+ if (mHiddenInvalidationObserverRefreshDriver) {
+ mHiddenInvalidationObserverRefreshDriver->RemovePresShellToInvalidateIfHidden(this);
+ }
+
+ if (rd->GetPresContext() == GetPresContext()) {
+ rd->RevokeViewManagerFlush();
+ }
+
+ mResizeEvent.Revoke();
+ if (mAsyncResizeTimerIsActive) {
+ mAsyncResizeEventTimer->Cancel();
+ mAsyncResizeTimerIsActive = false;
+ }
+
+ CancelAllPendingReflows();
+ CancelPostedReflowCallbacks();
+
+ // Destroy the frame manager. This will destroy the frame hierarchy
+ mFrameConstructor->WillDestroyFrameTree();
+
+ // Destroy all frame properties (whose destruction was suppressed
+ // while destroying the frame tree, but which might contain more
+ // frames within the properties.
+ if (mPresContext) {
+ // Clear out the prescontext's property table -- since our frame tree is
+ // now dead, we shouldn't be looking up any more properties in that table.
+ // We want to do this before we call DetachShell() on the prescontext, so
+ // property destructors can usefully call GetPresShell() on the
+ // prescontext.
+ mPresContext->PropertyTable()->DeleteAll();
+ }
+
+
+ NS_WARNING_ASSERTION(!mWeakFrames,
+ "Weak frames alive after destroying FrameManager");
+ while (mWeakFrames) {
+ mWeakFrames->Clear(this);
+ }
+
+ // Let the style set do its cleanup.
+ mStyleSet->Shutdown();
+
+ if (mPresContext) {
+ // We hold a reference to the pres context, and it holds a weak link back
+ // to us. To avoid the pres context having a dangling reference, set its
+ // pres shell to nullptr
+ mPresContext->DetachShell();
+
+ // Clear the link handler (weak reference) as well
+ mPresContext->SetLinkHandler(nullptr);
+ }
+
+ mHaveShutDown = true;
+
+ mTouchManager.Destroy();
+}
+
+void
+PresShell::MakeZombie()
+{
+ mIsZombie = true;
+ CancelAllPendingReflows();
+}
+
+nsRefreshDriver*
+nsIPresShell::GetRefreshDriver() const
+{
+ return mPresContext ? mPresContext->RefreshDriver() : nullptr;
+}
+
+void
+nsIPresShell::SetAuthorStyleDisabled(bool aStyleDisabled)
+{
+ if (aStyleDisabled != mStyleSet->GetAuthorStyleDisabled()) {
+ mStyleSet->SetAuthorStyleDisabled(aStyleDisabled);
+ RestyleForCSSRuleChanges();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(mDocument,
+ "author-style-disabled-changed",
+ nullptr);
+ }
+ }
+}
+
+bool
+nsIPresShell::GetAuthorStyleDisabled() const
+{
+ return mStyleSet->GetAuthorStyleDisabled();
+}
+
+void
+PresShell::UpdatePreferenceStyles()
+{
+ if (!mDocument) {
+ return;
+ }
+
+ // If the document doesn't have a window there's no need to notify
+ // its presshell about changes to preferences since the document is
+ // in a state where it doesn't matter any more (see
+ // nsDocumentViewer::Close()).
+ if (!mDocument->GetWindow()) {
+ return;
+ }
+
+ // Documents in chrome shells do not have any preference style rules applied.
+ if (nsContentUtils::IsInChromeDocshell(mDocument)) {
+ return;
+ }
+
+ // We need to pass in mPresContext so that if the nsLayoutStylesheetCache
+ // needs to recreate the pref style sheet, it has somewhere to get the
+ // pref styling information from. All pres contexts for
+ // IsChromeOriginImage() == false will have the same pref styling information,
+ // and similarly for IsChromeOriginImage() == true, so it doesn't really
+ // matter which pres context we pass in when it does need to be recreated.
+ // (See nsPresContext::GetDocumentColorPreferences for how whether we
+ // are a chrome origin image affects some pref styling information.)
+ auto cache = nsLayoutStylesheetCache::For(mStyleSet->BackendType());
+ RefPtr<StyleSheet> newPrefSheet =
+ mPresContext->IsChromeOriginImage() ?
+ cache->ChromePreferenceSheet(mPresContext) :
+ cache->ContentPreferenceSheet(mPresContext);
+
+ if (mPrefStyleSheet == newPrefSheet) {
+ return;
+ }
+
+ mStyleSet->BeginUpdate();
+
+ RemovePreferenceStyles();
+
+ mStyleSet->AppendStyleSheet(SheetType::User, newPrefSheet);
+ mPrefStyleSheet = newPrefSheet;
+
+ mStyleSet->EndUpdate();
+}
+
+void
+PresShell::RemovePreferenceStyles()
+{
+ if (mPrefStyleSheet) {
+ mStyleSet->RemoveStyleSheet(SheetType::User, mPrefStyleSheet);
+ mPrefStyleSheet = nullptr;
+ }
+}
+
+void
+PresShell::AddUserSheet(nsISupports* aSheet)
+{
+ if (mStyleSet->IsServo()) {
+ NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet");
+ return;
+ }
+
+ // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
+ // ordering. We want this new sheet to come after all the existing stylesheet
+ // service sheets, but before other user sheets; see nsIStyleSheetService.idl
+ // for the ordering. Just remove and readd all the nsStyleSheetService
+ // sheets.
+ nsCOMPtr<nsIStyleSheetService> dummy =
+ do_GetService(NS_STYLESHEETSERVICE_CONTRACTID);
+
+ mStyleSet->BeginUpdate();
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::gInstance;
+ nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();
+ // Iterate forwards when removing so the searches for RemoveStyleSheet are as
+ // short as possible.
+ for (StyleSheet* sheet : userSheets) {
+ mStyleSet->RemoveStyleSheet(SheetType::User, sheet);
+ }
+
+ // Now iterate backwards, so that the order of userSheets will be the same as
+ // the order of sheets from it in the style set.
+ for (StyleSheet* sheet : Reversed(userSheets)) {
+ mStyleSet->PrependStyleSheet(SheetType::User, sheet);
+ }
+
+ mStyleSet->EndUpdate();
+
+ RestyleForCSSRuleChanges();
+}
+
+void
+PresShell::AddAgentSheet(nsISupports* aSheet)
+{
+ // Make sure this does what nsDocumentViewer::CreateStyleSet does
+ // wrt ordering.
+ // XXXheycam This needs to work with ServoStyleSheets too.
+ RefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet);
+ if (!sheet) {
+ NS_ERROR("stylo: AddAgentSheet needs to support ServoStyleSheets");
+ return;
+ }
+
+ mStyleSet->AppendStyleSheet(SheetType::Agent, sheet);
+ RestyleForCSSRuleChanges();
+}
+
+void
+PresShell::AddAuthorSheet(nsISupports* aSheet)
+{
+ // XXXheycam This needs to work with ServoStyleSheets too.
+ RefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet);
+ if (!sheet) {
+ NS_ERROR("stylo: AddAuthorSheet needs to support ServoStyleSheets");
+ return;
+ }
+
+ // Document specific "additional" Author sheets should be stronger than the
+ // ones added with the StyleSheetService.
+ StyleSheet* firstAuthorSheet =
+ mDocument->GetFirstAdditionalAuthorSheet();
+ if (firstAuthorSheet) {
+ mStyleSet->InsertStyleSheetBefore(SheetType::Doc, sheet, firstAuthorSheet);
+ } else {
+ mStyleSet->AppendStyleSheet(SheetType::Doc, sheet);
+ }
+
+ RestyleForCSSRuleChanges();
+}
+
+void
+PresShell::RemoveSheet(SheetType aType, nsISupports* aSheet)
+{
+ RefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet);
+ if (!sheet) {
+ NS_ERROR("stylo: RemoveSheet needs to support ServoStyleSheets");
+ return;
+ }
+
+ mStyleSet->RemoveStyleSheet(aType, sheet);
+ RestyleForCSSRuleChanges();
+}
+
+NS_IMETHODIMP
+PresShell::SetDisplaySelection(int16_t aToggle)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->SetDisplaySelection(aToggle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::GetDisplaySelection(int16_t *aToggle)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ *aToggle = frameSelection->GetDisplaySelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::GetSelection(RawSelectionType aRawSelectionType,
+ nsISelection **aSelection)
+{
+ if (!aSelection || !mSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsCOMPtr<nsISelection> selection =
+ frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
+
+ if (!selection) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ selection.forget(aSelection);
+ return NS_OK;
+}
+
+Selection*
+PresShell::GetCurrentSelection(SelectionType aSelectionType)
+{
+ if (!mSelection)
+ return nullptr;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->GetSelection(aSelectionType);
+}
+
+already_AddRefed<nsISelectionController>
+PresShell::GetSelectionControllerForFocusedContent(nsIContent** aFocusedContent)
+{
+ if (aFocusedContent) {
+ *aFocusedContent = nullptr;
+ }
+
+ if (mDocument) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsCOMPtr<nsIContent> focusedContent =
+ nsFocusManager::GetFocusedDescendant(mDocument->GetWindow(), false,
+ getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ nsIFrame* frame = focusedContent->GetPrimaryFrame();
+ if (frame) {
+ nsCOMPtr<nsISelectionController> selectionController;
+ frame->GetSelectionController(mPresContext,
+ getter_AddRefs(selectionController));
+ if (selectionController) {
+ if (aFocusedContent) {
+ focusedContent.forget(aFocusedContent);
+ }
+ return selectionController.forget();
+ }
+ }
+ }
+ }
+ nsCOMPtr<nsISelectionController> self(this);
+ return self.forget();
+}
+
+NS_IMETHODIMP
+PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
+ SelectionRegion aRegion,
+ int16_t aFlags)
+{
+ if (!mSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->ScrollSelectionIntoView(
+ ToSelectionType(aRawSelectionType), aRegion, aFlags);
+}
+
+NS_IMETHODIMP
+PresShell::RepaintSelection(RawSelectionType aRawSelectionType)
+{
+ if (!mSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
+}
+
+// Make shell be a document observer
+void
+PresShell::BeginObservingDocument()
+{
+ if (mDocument && !mIsDestroying) {
+ mDocument->AddObserver(this);
+ if (mIsDocumentGone) {
+ NS_WARNING("Adding a presshell that was disconnected from the document "
+ "as a document observer? Sounds wrong...");
+ mIsDocumentGone = false;
+ }
+ }
+}
+
+// Make shell stop being a document observer
+void
+PresShell::EndObservingDocument()
+{
+ // XXXbz do we need to tell the frame constructor that the document
+ // is gone, perhaps? Except for printing it's NOT gone, sometimes.
+ mIsDocumentGone = true;
+ if (mDocument) {
+ mDocument->RemoveObserver(this);
+ }
+}
+
+#ifdef DEBUG_kipp
+char* nsPresShell_ReflowStackPointerTop;
+#endif
+
+class XBLConstructorRunner : public Runnable
+{
+public:
+ explicit XBLConstructorRunner(nsIDocument* aDocument)
+ : mDocument(aDocument)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mDocument->BindingManager()->ProcessAttachedQueue();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+nsresult
+PresShell::Initialize(nscoord aWidth, nscoord aHeight)
+{
+ if (mIsDestroying) {
+ return NS_OK;
+ }
+
+ if (!mDocument) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mDidInitialize, "Why are we being called?");
+
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+ mDidInitialize = true;
+
+#ifdef DEBUG
+ if (VERIFY_REFLOW_NOISY_RC & gVerifyReflowFlags) {
+ if (mDocument) {
+ nsIURI *uri = mDocument->GetDocumentURI();
+ if (uri) {
+ printf("*** PresShell::Initialize (this=%p, url='%s')\n",
+ (void*)this, uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+#endif
+
+ // XXX Do a full invalidate at the beginning so that invalidates along
+ // the way don't have region accumulation issues?
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+
+ if (mStyleSet->IsServo() && mDocument->GetRootElement()) {
+ // If we have the root element already, go ahead style it along with any
+ // descendants.
+ //
+ // Some things, like nsDocumentViewer::GetPageMode, recreate the PresShell
+ // while keeping the content tree alive (see bug 1292280) - so we
+ // unconditionally mark the root as dirty.
+ mDocument->GetRootElement()->SetIsDirtyForServo();
+ mStyleSet->AsServo()->StyleDocument(/* aLeaveDirtyBits = */ false);
+ }
+
+ // Get the root frame from the frame manager
+ // XXXbz it would be nice to move this somewhere else... like frame manager
+ // Init(), say. But we need to make sure our views are all set up by the
+ // time we do this!
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
+ if (!rootFrame) {
+ nsAutoScriptBlocker scriptBlocker;
+ mFrameConstructor->BeginUpdate();
+ rootFrame = mFrameConstructor->ConstructRootFrame();
+ mFrameConstructor->SetRootFrame(rootFrame);
+ mFrameConstructor->EndUpdate();
+ }
+
+ NS_ENSURE_STATE(!mHaveShutDown);
+
+ if (!rootFrame) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsIFrame* invalidateFrame = nullptr;
+ for (nsIFrame* f = rootFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+ if (f->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) {
+ invalidateFrame = f;
+ f->RemoveStateBits(NS_FRAME_NO_COMPONENT_ALPHA);
+ }
+ nsCOMPtr<nsIPresShell> shell;
+ if (f->GetType() == nsGkAtoms::subDocumentFrame &&
+ (shell = static_cast<nsSubDocumentFrame*>(f)->GetSubdocumentPresShellForPainting(0)) &&
+ shell->GetPresContext()->IsRootContentDocument()) {
+ // Root content documents build a 'force active' layer, and component alpha flattening
+ // can't be propagated across that so no need to invalidate above this frame.
+ break;
+ }
+
+
+ }
+ if (invalidateFrame) {
+ invalidateFrame->InvalidateFrameSubtree();
+ }
+
+ Element *root = mDocument->GetRootElement();
+
+ if (root) {
+ {
+ nsAutoCauseReflowNotifier reflowNotifier(this);
+ mFrameConstructor->BeginUpdate();
+
+ // Have the style sheet processor construct frame for the root
+ // content object down
+ mFrameConstructor->ContentInserted(nullptr, root, nullptr, false);
+ VERIFY_STYLE_TREE;
+
+ // Something in mFrameConstructor->ContentInserted may have caused
+ // Destroy() to get called, bug 337586.
+ NS_ENSURE_STATE(!mHaveShutDown);
+
+ mFrameConstructor->EndUpdate();
+ }
+
+ // nsAutoCauseReflowNotifier (which sets up a script blocker) going out of
+ // scope may have killed us too
+ NS_ENSURE_STATE(!mHaveShutDown);
+
+ // Run the XBL binding constructors for any new frames we've constructed.
+ // (Do this in a script runner, since our caller might have a script
+ // blocker on the stack.)
+ nsContentUtils::AddScriptRunner(new XBLConstructorRunner(mDocument));
+ }
+
+ NS_ASSERTION(rootFrame, "How did that happen?");
+
+ // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
+ // set, but XBL processing could have caused a reflow which clears it.
+ if (MOZ_LIKELY(rootFrame->GetStateBits() & NS_FRAME_IS_DIRTY)) {
+ // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
+ rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
+ "Why is the root in mDirtyRoots already?");
+ FrameNeedsReflow(rootFrame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
+ "Should be in mDirtyRoots now");
+ NS_ASSERTION(mReflowScheduled, "Why no reflow scheduled?");
+ }
+
+ // Restore our root scroll position now if we're getting here after EndLoad
+ // got called, since this is our one chance to do it. Note that we need not
+ // have reflowed for this to work; when the scrollframe is finally reflowed
+ // it'll pick up the position we store in it here.
+ if (!mDocumentLoading) {
+ RestoreRootScrollPosition();
+ }
+
+ // For printing, we just immediately unsuppress.
+ if (!mPresContext->IsPaginated()) {
+ // Kick off a one-shot timer based off our pref value. When this timer
+ // fires, if painting is still locked down, then we will go ahead and
+ // trigger a full invalidate and allow painting to proceed normally.
+ mPaintingSuppressed = true;
+ // Don't suppress painting if the document isn't loading.
+ nsIDocument::ReadyState readyState = mDocument->GetReadyStateEnum();
+ if (readyState != nsIDocument::READYSTATE_COMPLETE) {
+ mPaintSuppressionTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+ if (!mPaintSuppressionTimer) {
+ mPaintingSuppressed = false;
+ } else {
+ // Initialize the timer.
+
+ // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
+ int32_t delay =
+ Preferences::GetInt("nglayout.initialpaint.delay",
+ PAINTLOCK_EVENT_DELAY);
+
+ mPaintSuppressionTimer->InitWithNamedFuncCallback(
+ sPaintSuppressionCallback, this, delay, nsITimer::TYPE_ONE_SHOT,
+ "PresShell::sPaintSuppressionCallback");
+ }
+ }
+
+ // If we get here and painting is not suppressed, then we can paint anytime
+ // and we should fire the before-first-paint notification
+ if (!mPaintingSuppressed) {
+ ScheduleBeforeFirstPaint();
+ }
+
+ return NS_OK; //XXX this needs to be real. MMP
+}
+
+void
+PresShell::sPaintSuppressionCallback(nsITimer *aTimer, void* aPresShell)
+{
+ RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
+ if (self)
+ self->UnsuppressPainting();
+}
+
+void
+PresShell::AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell)
+{
+ static_cast<PresShell*>(aPresShell)->FireResizeEvent();
+}
+
+nsresult
+PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight)
+{
+ if (mZoomConstraintsClient) {
+ // If we have a ZoomConstraintsClient and the available screen area
+ // changed, then we might need to disable double-tap-to-zoom, so notify
+ // the ZCC to update itself.
+ mZoomConstraintsClient->ScreenSizeChanged();
+ }
+ if (mMobileViewportManager) {
+ // If we have a mobile viewport manager, request a reflow from it. It can
+ // recompute the final CSS viewport and trigger a call to
+ // ResizeReflowIgnoreOverride if it changed.
+ mMobileViewportManager->RequestReflow();
+ return NS_OK;
+ }
+
+ return ResizeReflowIgnoreOverride(aWidth, aHeight, aOldWidth, aOldHeight);
+}
+
+nsresult
+PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight)
+{
+ NS_PRECONDITION(!mIsReflowing, "Shouldn't be in reflow here!");
+
+ // If we don't have a root frame yet, that means we haven't had our initial
+ // reflow... If that's the case, and aWidth or aHeight is unconstrained,
+ // ignore them altogether.
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame && aHeight == NS_UNCONSTRAINEDSIZE) {
+ // We can't do the work needed for SizeToContent without a root
+ // frame, and we want to return before setting the visible area.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+
+ // There isn't anything useful we can do if the initial reflow hasn't happened.
+ if (!rootFrame) {
+ return NS_OK;
+ }
+
+ WritingMode wm = rootFrame->GetWritingMode();
+ NS_PRECONDITION((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
+ "shouldn't use unconstrained isize anymore");
+
+ const bool isBSizeChanging = wm.IsVertical()
+ ? aOldWidth != aWidth
+ : aOldHeight != aHeight;
+
+ RefPtr<nsViewManager> viewManager = mViewManager;
+ // Take this ref after viewManager so it'll make sure to go away first.
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+
+ if (!GetPresContext()->SuppressingResizeReflow()) {
+ // Have to make sure that the content notifications are flushed before we
+ // start messing with the frame model; otherwise we can get content doubling.
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ // Make sure style is up to date
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ mPresContext->RestyleManager()->ProcessPendingRestyles();
+ }
+
+ rootFrame = mFrameConstructor->GetRootFrame();
+ if (!mIsDestroying && rootFrame) {
+ // XXX Do a full invalidate at the beginning so that invalidates along
+ // the way don't have region accumulation issues?
+
+ if (isBSizeChanging) {
+ // For BSize changes driven by style, RestyleManager handles this.
+ // For height:auto BSizes (i.e. layout-controlled), descendant
+ // intrinsic sizes can't depend on them. So the only other case is
+ // viewport-controlled BSizes which we handle here.
+ nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
+ }
+
+ {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ WillDoReflow();
+
+ // Kick off a top-down reflow
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
+ nsViewManager::AutoDisableRefresh refreshBlocker(viewManager);
+
+ mDirtyRoots.RemoveElement(rootFrame);
+ DoReflow(rootFrame, true);
+ }
+
+ DidDoReflow(true);
+ }
+ }
+
+ rootFrame = mFrameConstructor->GetRootFrame();
+ if (rootFrame) {
+ wm = rootFrame->GetWritingMode();
+ if (wm.IsVertical()) {
+ if (aWidth == NS_UNCONSTRAINEDSIZE) {
+ mPresContext->SetVisibleArea(
+ nsRect(0, 0, rootFrame->GetRect().width, aHeight));
+ }
+ } else {
+ if (aHeight == NS_UNCONSTRAINEDSIZE) {
+ mPresContext->SetVisibleArea(
+ nsRect(0, 0, aWidth, rootFrame->GetRect().height));
+ }
+ }
+ }
+
+ if (!mIsDestroying && !mResizeEvent.IsPending() &&
+ !mAsyncResizeTimerIsActive) {
+ if (mInResize) {
+ if (!mAsyncResizeEventTimer) {
+ mAsyncResizeEventTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+ if (mAsyncResizeEventTimer) {
+ mAsyncResizeTimerIsActive = true;
+ mAsyncResizeEventTimer->InitWithFuncCallback(AsyncResizeEventCallback,
+ this, 15,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ RefPtr<nsRunnableMethod<PresShell> > resizeEvent =
+ NewRunnableMethod(this, &PresShell::FireResizeEvent);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(resizeEvent))) {
+ mResizeEvent = resizeEvent;
+ mDocument->SetNeedStyleFlush();
+ }
+ }
+ }
+
+ return NS_OK; //XXX this needs to be real. MMP
+}
+
+void
+PresShell::FireResizeEvent()
+{
+ if (mAsyncResizeTimerIsActive) {
+ mAsyncResizeTimerIsActive = false;
+ mAsyncResizeEventTimer->Cancel();
+ }
+ mResizeEvent.Revoke();
+
+ if (mIsDocumentGone)
+ return;
+
+ //Send resize event from here.
+ WidgetEvent event(true, mozilla::eResize);
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+ mInResize = true;
+ EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+ mInResize = false;
+ }
+}
+
+void
+PresShell::SetIgnoreFrameDestruction(bool aIgnore)
+{
+ if (mDocument) {
+ // We need to tell the ImageLoader to drop all its references to frames
+ // because they're about to go away and it won't get notifications of that.
+ mDocument->StyleImageLoader()->ClearFrames(mPresContext);
+ }
+ mIgnoreFrameDestruction = aIgnore;
+}
+
+void
+PresShell::NotifyDestroyingFrame(nsIFrame* aFrame)
+{
+ if (!mIgnoreFrameDestruction) {
+ mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
+
+ mFrameConstructor->NotifyDestroyingFrame(aFrame);
+
+ for (int32_t idx = mDirtyRoots.Length(); idx; ) {
+ --idx;
+ if (mDirtyRoots[idx] == aFrame) {
+ mDirtyRoots.RemoveElementAt(idx);
+ }
+ }
+
+ // Remove frame properties
+ mPresContext->NotifyDestroyingFrame(aFrame);
+
+ if (aFrame == mCurrentEventFrame) {
+ mCurrentEventContent = aFrame->GetContent();
+ mCurrentEventFrame = nullptr;
+ }
+
+ #ifdef DEBUG
+ if (aFrame == mDrawEventTargetFrame) {
+ mDrawEventTargetFrame = nullptr;
+ }
+ #endif
+
+ for (unsigned int i=0; i < mCurrentEventFrameStack.Length(); i++) {
+ if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
+ //One of our stack frames was deleted. Get its content so that when we
+ //pop it we can still get its new frame from its content
+ nsIContent *currentEventContent = aFrame->GetContent();
+ mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+ }
+
+ mFramesToDirty.RemoveEntry(aFrame);
+ } else {
+ // We must delete this property in situ so that its destructor removes the
+ // frame from FrameLayerBuilder::DisplayItemData::mFrameList -- otherwise
+ // the DisplayItemData destructor will use the destroyed frame when it
+ // tries to remove it from the (array) value of this property.
+ mPresContext->PropertyTable()->
+ Delete(aFrame, FrameLayerBuilder::LayerManagerDataProperty());
+ }
+}
+
+already_AddRefed<nsCaret> PresShell::GetCaret() const
+{
+ RefPtr<nsCaret> caret = mCaret;
+ return caret.forget();
+}
+
+already_AddRefed<AccessibleCaretEventHub> PresShell::GetAccessibleCaretEventHub() const
+{
+ RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
+ return eventHub.forget();
+}
+
+void PresShell::SetCaret(nsCaret *aNewCaret)
+{
+ mCaret = aNewCaret;
+}
+
+void PresShell::RestoreCaret()
+{
+ mCaret = mOriginalCaret;
+}
+
+NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable)
+{
+ bool oldEnabled = mCaretEnabled;
+
+ mCaretEnabled = aInEnable;
+
+ if (mCaretEnabled != oldEnabled)
+ {
+ MOZ_ASSERT(mCaret);
+ if (mCaret) {
+ mCaret->SetVisible(mCaretEnabled);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly)
+{
+ if (mCaret)
+ mCaret->SetCaretReadOnly(aReadOnly);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetCaretEnabled(bool *aOutEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aOutEnabled);
+ *aOutEnabled = mCaretEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility)
+{
+ if (mCaret)
+ mCaret->SetVisibilityDuringSelection(aVisibility);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetCaretVisible(bool *aOutIsVisible)
+{
+ *aOutIsVisible = false;
+ if (mCaret) {
+ *aOutIsVisible = mCaret->IsVisible();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aInEnable)
+{
+ mSelectionFlags = aInEnable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t *aOutEnable)
+{
+ if (!aOutEnable)
+ return NS_ERROR_INVALID_ARG;
+ *aOutEnable = mSelectionFlags;
+ return NS_OK;
+}
+
+//implementation of nsISelectionController
+
+NS_IMETHODIMP
+PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
+}
+
+NS_IMETHODIMP
+PresShell::CharacterMove(bool aForward, bool aExtend)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->CharacterMove(aForward, aExtend);
+}
+
+NS_IMETHODIMP
+PresShell::CharacterExtendForDelete()
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->CharacterExtendForDelete();
+}
+
+NS_IMETHODIMP
+PresShell::CharacterExtendForBackspace()
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->CharacterExtendForBackspace();
+}
+
+NS_IMETHODIMP
+PresShell::WordMove(bool aForward, bool aExtend)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsresult result = frameSelection->WordMove(aForward, aExtend);
+// if we can't go down/up any more we must then move caret completely to
+// end/beginning respectively.
+ if (NS_FAILED(result))
+ result = CompleteMove(aForward, aExtend);
+ return result;
+}
+
+NS_IMETHODIMP
+PresShell::WordExtendForDelete(bool aForward)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->WordExtendForDelete(aForward);
+}
+
+NS_IMETHODIMP
+PresShell::LineMove(bool aForward, bool aExtend)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsresult result = frameSelection->LineMove(aForward, aExtend);
+// if we can't go down/up any more we must then move caret completely to
+// end/beginning respectively.
+ if (NS_FAILED(result))
+ result = CompleteMove(aForward,aExtend);
+ return result;
+}
+
+NS_IMETHODIMP
+PresShell::IntraLineMove(bool aForward, bool aExtend)
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->IntraLineMove(aForward, aExtend);
+}
+
+
+
+NS_IMETHODIMP
+PresShell::PageMove(bool aForward, bool aExtend)
+{
+ nsIScrollableFrame *scrollableFrame =
+ GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+ if (!scrollableFrame)
+ return NS_OK;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->CommonPageMove(aForward, aExtend, scrollableFrame);
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS |
+ nsISelectionController::SCROLL_FOR_CARET_MOVE);
+}
+
+
+
+NS_IMETHODIMP
+PresShell::ScrollPage(bool aForward)
+{
+ nsIScrollableFrame* scrollFrame =
+ GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+ if (scrollFrame) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollPage);
+ scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ nsIScrollableFrame::PAGES,
+ nsIScrollableFrame::SMOOTH,
+ nullptr, nullptr,
+ nsIScrollableFrame::NOT_MOMENTUM,
+ nsIScrollableFrame::ENABLE_SNAP);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::ScrollLine(bool aForward)
+{
+ nsIScrollableFrame* scrollFrame =
+ GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+ if (scrollFrame) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollLine);
+
+ int32_t lineCount = Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
+ NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
+ scrollFrame->ScrollBy(nsIntPoint(0, aForward ? lineCount : -lineCount),
+ nsIScrollableFrame::LINES,
+ nsIScrollableFrame::SMOOTH,
+ nullptr, nullptr,
+ nsIScrollableFrame::NOT_MOMENTUM,
+ nsIScrollableFrame::ENABLE_SNAP);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::ScrollCharacter(bool aRight)
+{
+ nsIScrollableFrame* scrollFrame =
+ GetFrameToScrollAsScrollable(nsIPresShell::eHorizontal);
+ if (scrollFrame) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollCharacter);
+ int32_t h = Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
+ NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
+ scrollFrame->ScrollBy(nsIntPoint(aRight ? h : -h, 0),
+ nsIScrollableFrame::LINES,
+ nsIScrollableFrame::SMOOTH,
+ nullptr, nullptr,
+ nsIScrollableFrame::NOT_MOMENTUM,
+ nsIScrollableFrame::ENABLE_SNAP);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::CompleteScroll(bool aForward)
+{
+ nsIScrollableFrame* scrollFrame =
+ GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
+ if (scrollFrame) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadCompleteScroll);
+ scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ nsIScrollableFrame::WHOLE,
+ nsIScrollableFrame::SMOOTH,
+ nullptr, nullptr,
+ nsIScrollableFrame::NOT_MOMENTUM,
+ nsIScrollableFrame::ENABLE_SNAP);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::CompleteMove(bool aForward, bool aExtend)
+{
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsIContent* limiter = frameSelection->GetAncestorLimiter();
+ nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
+ : FrameConstructor()->GetRootElementFrame();
+ if (!frame)
+ return NS_ERROR_FAILURE;
+ nsIFrame::CaretPosition pos =
+ frame->GetExtremeCaretPosition(!aForward);
+ frameSelection->HandleClick(pos.mResultContent, pos.mContentOffset,
+ pos.mContentOffset, aExtend, false,
+ aForward ? CARET_ASSOCIATE_AFTER :
+ CARET_ASSOCIATE_BEFORE);
+ if (limiter) {
+ // HandleClick resets ancestorLimiter, so set it again.
+ frameSelection->SetAncestorLimiter(limiter);
+ }
+
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS |
+ nsISelectionController::SCROLL_FOR_CARET_MOVE);
+}
+
+NS_IMETHODIMP
+PresShell::SelectAll()
+{
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->SelectAll();
+}
+
+static void
+DoCheckVisibility(nsPresContext* aPresContext,
+ nsIContent* aNode,
+ int16_t aStartOffset,
+ int16_t aEndOffset,
+ bool* aRetval)
+{
+ nsIFrame* frame = aNode->GetPrimaryFrame();
+ if (!frame) {
+ // No frame to look at so it must not be visible.
+ return;
+ }
+
+ // Start process now to go through all frames to find startOffset. Then check
+ // chars after that to see if anything until EndOffset is visible.
+ bool finished = false;
+ frame->CheckVisibility(aPresContext, aStartOffset, aEndOffset, true,
+ &finished, aRetval);
+ // Don't worry about other return value.
+}
+
+NS_IMETHODIMP
+PresShell::CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool *_retval)
+{
+ if (!node || startOffset>EndOffset || !_retval || startOffset<0 || EndOffset<0)
+ return NS_ERROR_INVALID_ARG;
+ *_retval = false; //initialize return parameter
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ if (!content)
+ return NS_ERROR_FAILURE;
+
+ DoCheckVisibility(mPresContext, content, startOffset, EndOffset, _retval);
+ return NS_OK;
+}
+
+nsresult
+PresShell::CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset,
+ int16_t aEndOffset, bool* aRetval)
+{
+ if (!aNode || aStartOffset > aEndOffset || !aRetval ||
+ aStartOffset < 0 || aEndOffset < 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aRetval = false;
+ DoCheckVisibility(mPresContext, aNode, aStartOffset, aEndOffset, aRetval);
+ return NS_OK;
+}
+
+//end implementations nsISelectionController
+
+nsIFrame*
+nsIPresShell::GetRootFrameExternal() const
+{
+ return mFrameConstructor->GetRootFrame();
+}
+
+nsIFrame*
+nsIPresShell::GetRootScrollFrame() const
+{
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ // Ensure root frame is a viewport frame
+ if (!rootFrame || nsGkAtoms::viewportFrame != rootFrame->GetType())
+ return nullptr;
+ nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
+ if (!theFrame || nsGkAtoms::scrollFrame != theFrame->GetType())
+ return nullptr;
+ return theFrame;
+}
+
+nsIScrollableFrame*
+nsIPresShell::GetRootScrollFrameAsScrollable() const
+{
+ nsIFrame* frame = GetRootScrollFrame();
+ if (!frame)
+ return nullptr;
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
+ NS_ASSERTION(scrollableFrame,
+ "All scroll frames must implement nsIScrollableFrame");
+ return scrollableFrame;
+}
+
+nsIScrollableFrame*
+nsIPresShell::GetRootScrollFrameAsScrollableExternal() const
+{
+ return GetRootScrollFrameAsScrollable();
+}
+
+nsIPageSequenceFrame*
+PresShell::GetPageSequenceFrame() const
+{
+ nsIFrame* frame = mFrameConstructor->GetPageSequenceFrame();
+ return do_QueryFrame(frame);
+}
+
+nsCanvasFrame*
+PresShell::GetCanvasFrame() const
+{
+ nsIFrame* frame = mFrameConstructor->GetDocElementContainingBlock();
+ return do_QueryFrame(frame);
+}
+
+void
+PresShell::BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType)
+{
+#ifdef DEBUG
+ mUpdateCount++;
+#endif
+ mFrameConstructor->BeginUpdate();
+
+ if (aUpdateType & UPDATE_STYLE)
+ mStyleSet->BeginUpdate();
+}
+
+void
+PresShell::EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType)
+{
+#ifdef DEBUG
+ NS_PRECONDITION(0 != mUpdateCount, "too many EndUpdate's");
+ --mUpdateCount;
+#endif
+
+ if (aUpdateType & UPDATE_STYLE) {
+ mStyleSet->EndUpdate();
+ if (mStylesHaveChanged || !mChangedScopeStyleRoots.IsEmpty())
+ RestyleForCSSRuleChanges();
+ }
+
+ mFrameConstructor->EndUpdate();
+}
+
+void
+PresShell::RestoreRootScrollPosition()
+{
+ nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
+ if (scrollableFrame) {
+ scrollableFrame->ScrollToRestoredPosition();
+ }
+}
+
+void
+PresShell::MaybeReleaseCapturingContent()
+{
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (frameSelection) {
+ frameSelection->SetDragState(false);
+ }
+ if (gCaptureInfo.mContent &&
+ gCaptureInfo.mContent->OwnerDoc() == mDocument) {
+ SetCapturingContent(nullptr, 0);
+ }
+}
+
+void
+PresShell::BeginLoad(nsIDocument *aDocument)
+{
+ mDocumentLoading = true;
+
+ gfxTextPerfMetrics *tp = nullptr;
+ if (mPresContext) {
+ tp = mPresContext->GetTextPerfMetrics();
+ }
+
+ bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
+ if (shouldLog || tp) {
+ mLoadBegin = TimeStamp::Now();
+ }
+
+ if (shouldLog) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("(presshell) %p load begin [%s]\n",
+ this, uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+}
+
+void
+PresShell::EndLoad(nsIDocument *aDocument)
+{
+ NS_PRECONDITION(aDocument == mDocument, "Wrong document");
+
+ RestoreRootScrollPosition();
+
+ mDocumentLoading = false;
+}
+
+void
+PresShell::LoadComplete()
+{
+ gfxTextPerfMetrics *tp = nullptr;
+ if (mPresContext) {
+ tp = mPresContext->GetTextPerfMetrics();
+ }
+
+ // log load
+ bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
+ if (shouldLog || tp) {
+ TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
+ nsIURI* uri = mDocument->GetDocumentURI();
+ nsAutoCString spec;
+ if (uri) {
+ spec = uri->GetSpecOrDefault();
+ }
+ if (shouldLog) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("(presshell) %p load done time-ms: %9.2f [%s]\n",
+ this, loadTime.ToMilliseconds(), spec.get()));
+ }
+ if (tp) {
+ tp->Accumulate();
+ if (tp->cumulative.numChars > 0) {
+ LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
+ eLog_loaddone, spec.get());
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+void
+PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame)
+{
+ // XXXbz due to bug 372769, can't actually assert anything here...
+ return;
+
+ // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
+ // handles the root frame correctly.
+ if (!aFrame->GetParent()) {
+ return;
+ }
+
+ // Make sure that there is a reflow root ancestor of |aFrame| that's
+ // in mDirtyRoots already.
+ while (aFrame && (aFrame->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN)) {
+ if (((aFrame->GetStateBits() & NS_FRAME_REFLOW_ROOT) ||
+ !aFrame->GetParent()) &&
+ mDirtyRoots.Contains(aFrame)) {
+ return;
+ }
+
+ aFrame = aFrame->GetParent();
+ }
+ NS_NOTREACHED("Frame has dirty bits set but isn't scheduled to be "
+ "reflowed?");
+}
+#endif
+
+void
+PresShell::FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd,
+ ReflowRootHandling aRootHandling)
+{
+ NS_PRECONDITION(aBitToAdd == NS_FRAME_IS_DIRTY ||
+ aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN ||
+ !aBitToAdd,
+ "Unexpected bits being added");
+ NS_PRECONDITION(!(aIntrinsicDirty == eStyleChange &&
+ aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN),
+ "bits don't correspond to style change reason");
+
+ NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");
+
+ // If we've not yet done the initial reflow, then don't bother
+ // enqueuing a reflow command yet.
+ if (! mDidInitialize)
+ return;
+
+ // If we're already destroying, don't bother with this either.
+ if (mIsDestroying)
+ return;
+
+#ifdef DEBUG
+ //printf("gShellCounter: %d\n", gShellCounter++);
+ if (mInVerifyReflow)
+ return;
+
+ if (VERIFY_REFLOW_NOISY_RC & gVerifyReflowFlags) {
+ printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this, (void*)aFrame);
+ if (VERIFY_REFLOW_REALLY_NOISY_RC & gVerifyReflowFlags) {
+ printf("Current content model:\n");
+ Element *rootElement = mDocument->GetRootElement();
+ if (rootElement) {
+ rootElement->List(stdout, 0);
+ }
+ }
+ }
+#endif
+
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aFrame);
+
+ do {
+ nsIFrame *subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1);
+ subtrees.RemoveElementAt(subtrees.Length() - 1);
+
+ // Grab |wasDirty| now so we can go ahead and update the bits on
+ // subtreeRoot.
+ bool wasDirty = NS_SUBTREE_DIRTY(subtreeRoot);
+ subtreeRoot->AddStateBits(aBitToAdd);
+
+ // Determine whether we need to keep looking for the next ancestor
+ // reflow root if subtreeRoot itself is a reflow root.
+ bool targetNeedsReflowFromParent;
+ switch (aRootHandling) {
+ case ePositionOrSizeChange:
+ targetNeedsReflowFromParent = true;
+ break;
+ case eNoPositionOrSizeChange:
+ targetNeedsReflowFromParent = false;
+ break;
+ case eInferFromBitToAdd:
+ targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
+ break;
+ }
+
+#define FRAME_IS_REFLOW_ROOT(_f) \
+ ((_f->GetStateBits() & NS_FRAME_REFLOW_ROOT) && \
+ (_f != subtreeRoot || !targetNeedsReflowFromParent))
+
+
+ // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
+ // and all of its descendants, if needed:
+
+ if (aIntrinsicDirty != nsIPresShell::eResize) {
+ // Mark argument and all ancestors dirty. (Unless we hit a reflow
+ // root that should contain the reflow. That root could be
+ // subtreeRoot itself if it's not dirty, or it could be some
+ // ancestor of subtreeRoot.)
+ for (nsIFrame *a = subtreeRoot;
+ a && !FRAME_IS_REFLOW_ROOT(a);
+ a = a->GetParent())
+ a->MarkIntrinsicISizesDirty();
+ }
+
+ if (aIntrinsicDirty == eStyleChange) {
+ // Mark all descendants dirty (using an nsTArray stack rather than
+ // recursion).
+ // Note that ReflowInput::InitResizeFlags has some similar
+ // code; see comments there for how and why it differs.
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(subtreeRoot);
+
+ do {
+ nsIFrame *f = stack.ElementAt(stack.Length() - 1);
+ stack.RemoveElementAt(stack.Length() - 1);
+
+ if (f->GetType() == nsGkAtoms::placeholderFrame) {
+ nsIFrame *oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+
+ nsIFrame::ChildListIterator lists(f);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* kid : lists.CurrentList()) {
+ kid->MarkIntrinsicISizesDirty();
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+ }
+
+ // Skip setting dirty bits up the tree if we weren't given a bit to add.
+ if (!aBitToAdd) {
+ continue;
+ }
+
+ // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
+ // up the tree until we reach either a frame that's already dirty or
+ // a reflow root.
+ nsIFrame *f = subtreeRoot;
+ for (;;) {
+ if (FRAME_IS_REFLOW_ROOT(f) || !f->GetParent()) {
+ // we've hit a reflow root or the root frame
+ if (!wasDirty) {
+ mDirtyRoots.AppendElement(f);
+ mDocument->SetNeedLayoutFlush();
+ }
+#ifdef DEBUG
+ else {
+ VerifyHasDirtyRootAncestor(f);
+ }
+#endif
+
+ break;
+ }
+
+ nsIFrame *child = f;
+ f = f->GetParent();
+ wasDirty = NS_SUBTREE_DIRTY(f);
+ f->ChildIsDirty(child);
+ NS_ASSERTION(f->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN,
+ "ChildIsDirty didn't do its job");
+ if (wasDirty) {
+ // This frame was already marked dirty.
+#ifdef DEBUG
+ VerifyHasDirtyRootAncestor(f);
+#endif
+ break;
+ }
+ }
+ } while (subtrees.Length() != 0);
+
+ MaybeScheduleReflow();
+}
+
+void
+PresShell::FrameNeedsToContinueReflow(nsIFrame *aFrame)
+{
+ NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
+ NS_PRECONDITION(mCurrentReflowRoot, "Must have a current reflow root here");
+ NS_ASSERTION(aFrame == mCurrentReflowRoot ||
+ nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
+ "Frame passed in is not the descendant of mCurrentReflowRoot");
+ NS_ASSERTION(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW,
+ "Frame passed in not in reflow?");
+
+ mFramesToDirty.PutEntry(aFrame);
+}
+
+nsIScrollableFrame*
+nsIPresShell::GetFrameToScrollAsScrollable(
+ nsIPresShell::ScrollDirection aDirection)
+{
+ nsIScrollableFrame* scrollFrame = nullptr;
+
+ nsCOMPtr<nsIContent> focusedContent;
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && mDocument) {
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
+ getter_AddRefs(focusedElement));
+ focusedContent = do_QueryInterface(focusedElement);
+ }
+ if (!focusedContent && mSelection) {
+ nsISelection* domSelection =
+ mSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ nsCOMPtr<nsIDOMNode> focusedNode;
+ domSelection->GetFocusNode(getter_AddRefs(focusedNode));
+ focusedContent = do_QueryInterface(focusedNode);
+ }
+ }
+ if (focusedContent) {
+ nsIFrame* startFrame = focusedContent->GetPrimaryFrame();
+ if (startFrame) {
+ scrollFrame = startFrame->GetScrollTargetFrame();
+ if (scrollFrame) {
+ startFrame = scrollFrame->GetScrolledFrame();
+ }
+ if (aDirection == nsIPresShell::eEither) {
+ scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(startFrame);
+ } else {
+ scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrameForDirection(startFrame,
+ aDirection == eVertical ? nsLayoutUtils::eVertical :
+ nsLayoutUtils::eHorizontal);
+ }
+ }
+ }
+ if (!scrollFrame) {
+ scrollFrame = GetRootScrollFrameAsScrollable();
+ }
+ return scrollFrame;
+}
+
+void
+PresShell::CancelAllPendingReflows()
+{
+ mDirtyRoots.Clear();
+
+ if (mReflowScheduled) {
+ GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
+ mReflowScheduled = false;
+ }
+
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+void
+PresShell::DestroyFramesFor(nsIContent* aContent,
+ nsIContent** aDestroyedFramesFor)
+{
+ MOZ_ASSERT(aContent);
+ NS_ENSURE_TRUE_VOID(mPresContext);
+ if (!mDidInitialize) {
+ return;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Mark ourselves as not safe to flush while we're doing frame destruction.
+ ++mChangeNestCount;
+
+ nsCSSFrameConstructor* fc = FrameConstructor();
+ fc->BeginUpdate();
+ fc->DestroyFramesFor(aContent, aDestroyedFramesFor);
+ fc->EndUpdate();
+
+ --mChangeNestCount;
+}
+
+void
+PresShell::CreateFramesFor(nsIContent* aContent)
+{
+ NS_ENSURE_TRUE_VOID(mPresContext);
+ if (!mDidInitialize) {
+ // Nothing to do here. In fact, if we proceed and aContent is the
+ // root we will crash.
+ return;
+ }
+
+ // Don't call RecreateFramesForContent since that is not exported and we want
+ // to keep the number of entrypoints down.
+
+ NS_ASSERTION(mViewManager, "Should have view manager");
+ MOZ_ASSERT(aContent);
+
+ // Have to make sure that the content notifications are flushed before we
+ // start messing with the frame model; otherwise we can get content doubling.
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Mark ourselves as not safe to flush while we're doing frame construction.
+ ++mChangeNestCount;
+
+ nsCSSFrameConstructor* fc = FrameConstructor();
+ nsILayoutHistoryState* layoutState = fc->GetLastCapturedLayoutHistoryState();
+ fc->BeginUpdate();
+ fc->ContentInserted(aContent->GetParent(), aContent, layoutState, false);
+ fc->EndUpdate();
+
+ --mChangeNestCount;
+}
+
+nsresult
+PresShell::RecreateFramesFor(nsIContent* aContent)
+{
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_FAILURE);
+ if (!mDidInitialize) {
+ // Nothing to do here. In fact, if we proceed and aContent is the
+ // root we will crash.
+ return NS_OK;
+ }
+
+ // Don't call RecreateFramesForContent since that is not exported and we want
+ // to keep the number of entrypoints down.
+
+ NS_ASSERTION(mViewManager, "Should have view manager");
+
+ // Have to make sure that the content notifications are flushed before we
+ // start messing with the frame model; otherwise we can get content doubling.
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsStyleChangeList changeList;
+ changeList.AppendChange(nullptr, aContent, nsChangeHint_ReconstructFrame);
+
+ // Mark ourselves as not safe to flush while we're doing frame construction.
+ ++mChangeNestCount;
+ RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
+ nsresult rv = restyleManager->ProcessRestyledFrames(changeList);
+ restyleManager->FlushOverflowChangedTracker();
+ --mChangeNestCount;
+
+ return rv;
+}
+
+void
+nsIPresShell::PostRecreateFramesFor(Element* aElement)
+{
+ mPresContext->RestyleManager()->PostRestyleEvent(aElement, nsRestyleHint(0),
+ nsChangeHint_ReconstructFrame);
+}
+
+void
+nsIPresShell::RestyleForAnimation(Element* aElement, nsRestyleHint aHint)
+{
+ // Now that we no longer have separate non-animation and animation
+ // restyles, this method having a distinct identity is less important,
+ // but it still seems useful to offer as a "more public" API and as a
+ // chokepoint for these restyles to go through.
+ mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
+ nsChangeHint(0));
+}
+
+void
+nsIPresShell::SetForwardingContainer(const WeakPtr<nsDocShell> &aContainer)
+{
+ mForwardingContainer = aContainer;
+}
+
+void
+PresShell::ClearFrameRefs(nsIFrame* aFrame)
+{
+ mPresContext->EventStateManager()->ClearFrameRefs(aFrame);
+
+ nsWeakFrame* weakFrame = mWeakFrames;
+ while (weakFrame) {
+ nsWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
+ if (weakFrame->GetFrame() == aFrame) {
+ // This removes weakFrame from mWeakFrames.
+ weakFrame->Clear(this);
+ }
+ weakFrame = prev;
+ }
+}
+
+already_AddRefed<gfxContext>
+PresShell::CreateReferenceRenderingContext()
+{
+ nsDeviceContext* devCtx = mPresContext->DeviceContext();
+ RefPtr<gfxContext> rc;
+ if (mPresContext->IsScreen()) {
+ rc = gfxContext::CreateOrNull(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
+ } else {
+ // We assume the devCtx has positive width and height for this call.
+ // However, width and height, may be outside of the reasonable range
+ // so rc may still be null.
+ rc = devCtx->CreateReferenceRenderingContext();
+ }
+
+ return rc ? rc.forget() : nullptr;
+}
+
+nsresult
+PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
+ uint32_t aAdditionalScrollFlags)
+{
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const Element *root = mDocument->GetRootElement();
+ if (root && root->IsSVGElement(nsGkAtoms::svg)) {
+ // We need to execute this even if there is an empty anchor name
+ // so that any existing SVG fragment identifier effect is removed
+ if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument, aAnchorName)) {
+ return NS_OK;
+ }
+ }
+
+ // Hold a reference to the ESM in case event dispatch tears us down.
+ RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
+
+ if (aAnchorName.IsEmpty()) {
+ NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
+ esm->SetContentState(nullptr, NS_EVENT_STATE_URLTARGET);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIContent> content;
+
+ // Search for an element with a matching "id" attribute
+ if (mDocument) {
+ content = mDocument->GetElementById(aAnchorName);
+ }
+
+ // Search for an anchor element with a matching "name" attribute
+ if (!content && htmlDoc) {
+ nsCOMPtr<nsIDOMNodeList> list;
+ // Find a matching list of named nodes
+ rv = htmlDoc->GetElementsByName(aAnchorName, getter_AddRefs(list));
+ if (NS_SUCCEEDED(rv) && list) {
+ uint32_t i;
+ // Loop through the named nodes looking for the first anchor
+ for (i = 0; true; i++) {
+ nsCOMPtr<nsIDOMNode> node;
+ rv = list->Item(i, getter_AddRefs(node));
+ if (!node) { // End of list
+ break;
+ }
+ // Ensure it's an anchor element
+ content = do_QueryInterface(node);
+ if (content) {
+ if (content->IsHTMLElement(nsGkAtoms::a)) {
+ break;
+ }
+ content = nullptr;
+ }
+ }
+ }
+ }
+
+ // Search for anchor in the HTML namespace with a matching name
+ if (!content && !htmlDoc)
+ {
+ nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
+ nsCOMPtr<nsIDOMNodeList> list;
+ NS_NAMED_LITERAL_STRING(nameSpace, "http://www.w3.org/1999/xhtml");
+ // Get the list of anchor elements
+ rv = doc->GetElementsByTagNameNS(nameSpace, NS_LITERAL_STRING("a"), getter_AddRefs(list));
+ if (NS_SUCCEEDED(rv) && list) {
+ uint32_t i;
+ // Loop through the named nodes looking for the first anchor
+ for (i = 0; true; i++) {
+ nsCOMPtr<nsIDOMNode> node;
+ rv = list->Item(i, getter_AddRefs(node));
+ if (!node) { // End of list
+ break;
+ }
+ // Compare the name attribute
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
+ nsAutoString value;
+ if (element && NS_SUCCEEDED(element->GetAttribute(NS_LITERAL_STRING("name"), value))) {
+ if (value.Equals(aAnchorName)) {
+ content = do_QueryInterface(element);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ esm->SetContentState(content, NS_EVENT_STATE_URLTARGET);
+
+#ifdef ACCESSIBILITY
+ nsIContent *anchorTarget = content;
+#endif
+
+ nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
+ if (rootScroll && rootScroll->DidHistoryRestore()) {
+ // Scroll position restored from history trumps scrolling to anchor.
+ aScroll = false;
+ rootScroll->ClearDidHistoryRestore();
+ }
+
+ if (content) {
+ if (aScroll) {
+ rv = ScrollContentIntoView(content,
+ ScrollAxis(SCROLL_TOP, SCROLL_ALWAYS),
+ ScrollAxis(),
+ ANCHOR_SCROLL_FLAGS | aAdditionalScrollFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
+ if (rootScroll) {
+ mLastAnchorScrolledTo = content;
+ mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
+ }
+ }
+
+ // Should we select the target? This action is controlled by a
+ // preference: the default is to not select.
+ bool selectAnchor = Preferences::GetBool("layout.selectanchor");
+
+ // Even if select anchor pref is false, we must still move the
+ // caret there. That way tabbing will start from the new
+ // location
+ RefPtr<nsIDOMRange> jumpToRange = new nsRange(mDocument);
+ while (content && content->GetFirstChild()) {
+ content = content->GetFirstChild();
+ }
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
+ NS_ASSERTION(node, "No nsIDOMNode for descendant of anchor");
+ jumpToRange->SelectNodeContents(node);
+ // Select the anchor
+ RefPtr<Selection> sel = mSelection->GetSelection(SelectionType::eNormal);
+ if (sel) {
+ sel->RemoveAllRanges();
+ sel->AddRange(jumpToRange);
+ if (!selectAnchor) {
+ // Use a caret (collapsed selection) at the start of the anchor
+ sel->CollapseToStart();
+ }
+ }
+ // Selection is at anchor.
+ // Now focus the document itself if focus is on an element within it.
+ nsPIDOMWindowOuter *win = mDocument->GetWindow();
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && win) {
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+ if (SameCOMIdentity(win, focusedWindow)) {
+ fm->ClearFocus(focusedWindow);
+ }
+ }
+
+ // If the target is an animation element, activate the animation
+ if (content->IsNodeOfType(nsINode::eANIMATION)) {
+ SVGContentUtils::ActivateByHyperlink(content.get());
+ }
+ } else {
+ rv = NS_ERROR_FAILURE;
+ NS_NAMED_LITERAL_STRING(top, "top");
+ if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, top)) {
+ // Scroll to the top/left if aAnchorName is "top" and there is no element
+ // with such a name or id.
+ rv = NS_OK;
+ nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
+ // Check |aScroll| after setting |rv| so we set |rv| to the same
+ // thing whether or not |aScroll| is true.
+ if (aScroll && sf) {
+ // Scroll to the top of the page
+ sf->ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
+ }
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ if (anchorTarget) {
+ nsAccessibilityService* accService = AccService();
+ if (accService)
+ accService->NotifyOfAnchorJumpTo(anchorTarget);
+ }
+#endif
+
+ return rv;
+}
+
+nsresult
+PresShell::ScrollToAnchor()
+{
+ if (!mLastAnchorScrolledTo) {
+ return NS_OK;
+ }
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+
+ nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
+ if (!rootScroll ||
+ mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
+ return NS_OK;
+ }
+ nsresult rv = ScrollContentIntoView(mLastAnchorScrolledTo,
+ ScrollAxis(SCROLL_TOP, SCROLL_ALWAYS),
+ ScrollAxis(),
+ ANCHOR_SCROLL_FLAGS);
+ mLastAnchorScrolledTo = nullptr;
+ return rv;
+}
+
+/*
+ * Helper (per-continuation) for ScrollContentIntoView.
+ *
+ * @param aContainerFrame [in] the frame which aRect is relative to
+ * @param aFrame [in] Frame whose bounds should be unioned
+ * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
+ * we should include the top of the line in the added rectangle
+ * @param aRect [inout] rect into which its bounds should be unioned
+ * @param aHaveRect [inout] whether aRect contains data yet
+ * @param aPrevBlock [inout] the block aLines is a line iterator for
+ * @param aLines [inout] the line iterator we're using
+ * @param aCurLine [inout] the line to start looking from in this iterator
+ */
+static void
+AccumulateFrameBounds(nsIFrame* aContainerFrame,
+ nsIFrame* aFrame,
+ bool aUseWholeLineHeightForInlines,
+ nsRect& aRect,
+ bool& aHaveRect,
+ nsIFrame*& aPrevBlock,
+ nsAutoLineIterator& aLines,
+ int32_t& aCurLine)
+{
+ nsIFrame* frame = aFrame;
+ nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());
+
+ // If this is an inline frame and either the bounds height is 0 (quirks
+ // layout model) or aUseWholeLineHeightForInlines is set, we need to
+ // change the top of the bounds to include the whole line.
+ if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
+ nsIFrame *prevFrame = aFrame;
+ nsIFrame *f = aFrame;
+
+ while (f && f->IsFrameOfType(nsIFrame::eLineParticipant) &&
+ !f->IsTransformed() && !f->IsAbsPosContainingBlock()) {
+ prevFrame = f;
+ f = prevFrame->GetParent();
+ }
+
+ if (f != aFrame &&
+ f &&
+ f->GetType() == nsGkAtoms::blockFrame) {
+ // find the line containing aFrame and increase the top of |offset|.
+ if (f != aPrevBlock) {
+ aLines = f->GetLineIterator();
+ aPrevBlock = f;
+ aCurLine = 0;
+ }
+ if (aLines) {
+ int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
+ if (index >= 0) {
+ aCurLine = index;
+ nsIFrame *trash1;
+ int32_t trash2;
+ nsRect lineBounds;
+
+ if (NS_SUCCEEDED(aLines->GetLine(index, &trash1, &trash2,
+ lineBounds))) {
+ frameBounds += frame->GetOffsetTo(f);
+ frame = f;
+ if (lineBounds.y < frameBounds.y) {
+ frameBounds.height = frameBounds.YMost() - lineBounds.y;
+ frameBounds.y = lineBounds.y;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(frame,
+ frameBounds, aContainerFrame);
+
+ if (aHaveRect) {
+ // We can't use nsRect::UnionRect since it drops empty rects on
+ // the floor, and we need to include them. (Thus we need
+ // aHaveRect to know when to drop the initial value on the floor.)
+ aRect.UnionRectEdges(aRect, transformedBounds);
+ } else {
+ aHaveRect = true;
+ aRect = transformedBounds;
+ }
+}
+
+static bool
+ComputeNeedToScroll(nsIPresShell::WhenToScroll aWhenToScroll,
+ nscoord aLineSize,
+ nscoord aRectMin,
+ nscoord aRectMax,
+ nscoord aViewMin,
+ nscoord aViewMax) {
+ // See how the rect should be positioned vertically
+ if (nsIPresShell::SCROLL_ALWAYS == aWhenToScroll) {
+ // The caller wants the frame as visible as possible
+ return true;
+ } else if (nsIPresShell::SCROLL_IF_NOT_VISIBLE == aWhenToScroll) {
+ // Scroll only if no part of the frame is visible in this view
+ return aRectMax - aLineSize <= aViewMin ||
+ aRectMin + aLineSize >= aViewMax;
+ } else if (nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE == aWhenToScroll) {
+ // Scroll only if part of the frame is hidden and more can fit in view
+ return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
+ std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) < aViewMax - aViewMin;
+ }
+ return false;
+}
+
+static nscoord
+ComputeWhereToScroll(int16_t aWhereToScroll,
+ nscoord aOriginalCoord,
+ nscoord aRectMin,
+ nscoord aRectMax,
+ nscoord aViewMin,
+ nscoord aViewMax,
+ nscoord* aRangeMin,
+ nscoord* aRangeMax) {
+ nscoord resultCoord = aOriginalCoord;
+ // Allow the scroll operation to land anywhere that
+ // makes the whole rectangle visible.
+ if (nsIPresShell::SCROLL_MINIMUM == aWhereToScroll) {
+ if (aRectMin < aViewMin) {
+ // Scroll up so the frame's top edge is visible
+ resultCoord = aRectMin;
+ } else if (aRectMax > aViewMax) {
+ // Scroll down so the frame's bottom edge is visible. Make sure the
+ // frame's top edge is still visible
+ resultCoord = aOriginalCoord + aRectMax - aViewMax;
+ if (resultCoord > aRectMin) {
+ resultCoord = aRectMin;
+ }
+ }
+ } else {
+ nscoord frameAlignCoord =
+ NSToCoordRound(aRectMin + (aRectMax - aRectMin) * (aWhereToScroll / 100.0f));
+ resultCoord = NSToCoordRound(frameAlignCoord - (aViewMax - aViewMin) * (
+ aWhereToScroll / 100.0f));
+ }
+ nscoord scrollPortLength = aViewMax - aViewMin;
+ // Force the scroll range to extend to include resultCoord.
+ *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
+ *aRangeMax = std::max(resultCoord, aRectMin);
+ return resultCoord;
+}
+
+/**
+ * This function takes a scrollable frame, a rect in the coordinate system
+ * of the scrolled frame, and a desired percentage-based scroll
+ * position and attempts to scroll the rect to that position in the
+ * scrollport.
+ *
+ * This needs to work even if aRect has a width or height of zero.
+ */
+static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
+ const nsRect& aRect,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal,
+ uint32_t aFlags)
+{
+ nsPoint scrollPt = aFrameAsScrollable->GetScrollPosition();
+ nsRect visibleRect(scrollPt,
+ aFrameAsScrollable->GetScrollPositionClampingScrollPortSize());
+
+ nsSize lineSize;
+ // Don't call GetLineScrollAmount unless we actually need it. Not only
+ // does this save time, but it's not safe to call GetLineScrollAmount
+ // during reflow (because it depends on font size inflation and doesn't
+ // use the in-reflow-safe font-size inflation path). If we did call it,
+ // it would assert and possible give the wrong result.
+ if (aVertical.mWhenToScroll == nsIPresShell::SCROLL_IF_NOT_VISIBLE ||
+ aHorizontal.mWhenToScroll == nsIPresShell::SCROLL_IF_NOT_VISIBLE) {
+ lineSize = aFrameAsScrollable->GetLineScrollAmount();
+ }
+ ScrollbarStyles ss = aFrameAsScrollable->GetScrollbarStyles();
+ nsRect allowedRange(scrollPt, nsSize(0, 0));
+ bool needToScroll = false;
+ uint32_t directions = aFrameAsScrollable->GetPerceivedScrollingDirections();
+
+ if (((aFlags & nsIPresShell::SCROLL_OVERFLOW_HIDDEN) ||
+ ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
+ (!aVertical.mOnlyIfPerceivedScrollableDirection ||
+ (directions & nsIScrollableFrame::VERTICAL))) {
+
+ if (ComputeNeedToScroll(aVertical.mWhenToScroll,
+ lineSize.height,
+ aRect.y,
+ aRect.YMost(),
+ visibleRect.y,
+ visibleRect.YMost())) {
+ nscoord maxHeight;
+ scrollPt.y = ComputeWhereToScroll(aVertical.mWhereToScroll,
+ scrollPt.y,
+ aRect.y,
+ aRect.YMost(),
+ visibleRect.y,
+ visibleRect.YMost(),
+ &allowedRange.y, &maxHeight);
+ allowedRange.height = maxHeight - allowedRange.y;
+ needToScroll = true;
+ }
+ }
+
+ if (((aFlags & nsIPresShell::SCROLL_OVERFLOW_HIDDEN) ||
+ ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) &&
+ (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
+ (directions & nsIScrollableFrame::HORIZONTAL))) {
+
+ if (ComputeNeedToScroll(aHorizontal.mWhenToScroll,
+ lineSize.width,
+ aRect.x,
+ aRect.XMost(),
+ visibleRect.x,
+ visibleRect.XMost())) {
+ nscoord maxWidth;
+ scrollPt.x = ComputeWhereToScroll(aHorizontal.mWhereToScroll,
+ scrollPt.x,
+ aRect.x,
+ aRect.XMost(),
+ visibleRect.x,
+ visibleRect.XMost(),
+ &allowedRange.x, &maxWidth);
+ allowedRange.width = maxWidth - allowedRange.x;
+ needToScroll = true;
+ }
+ }
+
+ // If we don't need to scroll, then don't try since it might cancel
+ // a current smooth scroll operation.
+ if (needToScroll) {
+ nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+ bool autoBehaviorIsSmooth = (aFrameAsScrollable->GetScrollbarStyles().mScrollBehavior
+ == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH);
+ bool smoothScroll = (aFlags & nsIPresShell::SCROLL_SMOOTH) ||
+ ((aFlags & nsIPresShell::SCROLL_SMOOTH_AUTO) && autoBehaviorIsSmooth);
+ if (gfxPrefs::ScrollBehaviorEnabled() && smoothScroll) {
+ scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+ }
+ aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange);
+ }
+}
+
+nsresult
+PresShell::ScrollContentIntoView(nsIContent* aContent,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal,
+ uint32_t aFlags)
+{
+ NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIDocument> composedDoc = aContent->GetComposedDoc();
+ NS_ENSURE_STATE(composedDoc);
+
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+
+ if (mContentToScrollTo) {
+ mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
+ }
+ mContentToScrollTo = aContent;
+ ScrollIntoViewData* data = new ScrollIntoViewData();
+ data->mContentScrollVAxis = aVertical;
+ data->mContentScrollHAxis = aHorizontal;
+ data->mContentToScrollToFlags = aFlags;
+ if (NS_FAILED(mContentToScrollTo->SetProperty(nsGkAtoms::scrolling, data,
+ nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
+ mContentToScrollTo = nullptr;
+ }
+
+ // Flush layout and attempt to scroll in the process.
+ composedDoc->SetNeedLayoutFlush();
+ composedDoc->FlushPendingNotifications(Flush_InterruptibleLayout);
+
+ // If mContentToScrollTo is non-null, that means we interrupted the reflow
+ // (or suppressed it altogether because we're suppressing interruptible
+ // flushes right now) and won't necessarily get the position correct, but do
+ // a best-effort scroll here. The other option would be to do this inside
+ // FlushPendingNotifications, but I'm not sure the repeated scrolling that
+ // could trigger if reflows keep getting interrupted would be more desirable
+ // than a single best-effort scroll followed by one final scroll on the first
+ // completed reflow.
+ if (mContentToScrollTo) {
+ DoScrollContentIntoView();
+ }
+ return NS_OK;
+}
+
+void
+PresShell::DoScrollContentIntoView()
+{
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+
+ nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
+ if (!frame) {
+ mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ return;
+ }
+
+ if (frame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
+ // The reflow flush before this scroll got interrupted, and this frame's
+ // coords and size are all zero, and it has no content showing anyway.
+ // Don't bother scrolling to it. We'll try again when we finish up layout.
+ return;
+ }
+
+ // Make sure we skip 'frame' ... if it's scrollable, we should use its
+ // scrollable ancestor as the container.
+ nsIFrame* container =
+ nsLayoutUtils::GetClosestFrameOfType(frame->GetParent(), nsGkAtoms::scrollFrame);
+ if (!container) {
+ // nothing can be scrolled
+ return;
+ }
+
+ ScrollIntoViewData* data = static_cast<ScrollIntoViewData*>(
+ mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
+ if (MOZ_UNLIKELY(!data)) {
+ mContentToScrollTo = nullptr;
+ return;
+ }
+
+ // This is a two-step process.
+ // Step 1: Find the bounds of the rect we want to scroll into view. For
+ // example, for an inline frame we may want to scroll in the whole
+ // line, or we may want to scroll multiple lines into view.
+ // Step 2: Walk container frame and its ancestors and scroll them
+ // appropriately.
+ // frameBounds is relative to container. We're assuming
+ // that scrollframes don't split so every continuation of frame will
+ // be a descendant of container. (Things would still mostly work
+ // even if that assumption was false.)
+ nsRect frameBounds;
+ bool haveRect = false;
+ bool useWholeLineHeightForInlines =
+ data->mContentScrollVAxis.mWhenToScroll != nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ // Reuse the same line iterator across calls to AccumulateFrameBounds. We set
+ // it every time we detect a new block (stored in prevBlock).
+ nsIFrame* prevBlock = nullptr;
+ nsAutoLineIterator lines;
+ // The last line we found a continuation on in |lines|. We assume that later
+ // continuations cannot come on earlier lines.
+ int32_t curLine = 0;
+ do {
+ AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
+ frameBounds, haveRect, prevBlock, lines, curLine);
+ } while ((frame = frame->GetNextContinuation()));
+
+ ScrollFrameRectIntoView(container, frameBounds, data->mContentScrollVAxis,
+ data->mContentScrollHAxis,
+ data->mContentToScrollToFlags);
+}
+
+bool
+PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame,
+ const nsRect& aRect,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal,
+ uint32_t aFlags)
+{
+ bool didScroll = false;
+ // This function needs to work even if rect has a width or height of 0.
+ nsRect rect = aRect;
+ nsIFrame* container = aFrame;
+ // Walk up the frame hierarchy scrolling the rect into view and
+ // keeping rect relative to container
+ do {
+ nsIScrollableFrame* sf = do_QueryFrame(container);
+ if (sf) {
+ nsPoint oldPosition = sf->GetScrollPosition();
+ nsRect targetRect = rect;
+ if (container->StyleDisplay()->mOverflowClipBox ==
+ NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX) {
+ nsMargin padding = container->GetUsedPadding();
+ targetRect.Inflate(padding);
+ }
+ ScrollToShowRect(sf, targetRect - sf->GetScrolledFrame()->GetPosition(),
+ aVertical, aHorizontal, aFlags);
+ nsPoint newPosition = sf->LastScrollDestination();
+ // If the scroll position increased, that means our content moved up,
+ // so our rect's offset should decrease
+ rect += oldPosition - newPosition;
+
+ if (oldPosition != newPosition) {
+ didScroll = true;
+ }
+
+ // only scroll one container when this flag is set
+ if (aFlags & nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY) {
+ break;
+ }
+ }
+ nsIFrame* parent;
+ if (container->IsTransformed()) {
+ container->GetTransformMatrix(nullptr, &parent);
+ rect = nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
+ } else {
+ rect += container->GetPosition();
+ parent = container->GetParent();
+ }
+ if (!parent && !(aFlags & nsIPresShell::SCROLL_NO_PARENT_FRAMES)) {
+ nsPoint extraOffset(0,0);
+ parent = nsLayoutUtils::GetCrossDocParentFrame(container, &extraOffset);
+ if (parent) {
+ int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
+ int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
+ rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
+ rect += extraOffset;
+ }
+ }
+ container = parent;
+ } while (container);
+
+ return didScroll;
+}
+
+nsRectVisibility
+PresShell::GetRectVisibility(nsIFrame* aFrame,
+ const nsRect &aRect,
+ nscoord aMinTwips) const
+{
+ NS_ASSERTION(aFrame->PresContext() == GetPresContext(),
+ "prescontext mismatch?");
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ NS_ASSERTION(rootFrame,
+ "How can someone have a frame for this presshell when there's no root?");
+ nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
+ nsRect scrollPortRect;
+ if (sf) {
+ scrollPortRect = sf->GetScrollPortRect();
+ nsIFrame* f = do_QueryFrame(sf);
+ scrollPortRect += f->GetOffsetTo(rootFrame);
+ } else {
+ scrollPortRect = nsRect(nsPoint(0,0), rootFrame->GetSize());
+ }
+
+ nsRect r = aRect + aFrame->GetOffsetTo(rootFrame);
+ // If aRect is entirely visible then we don't need to ensure that
+ // at least aMinTwips of it is visible
+ if (scrollPortRect.Contains(r))
+ return nsRectVisibility_kVisible;
+
+ nsRect insetRect = scrollPortRect;
+ insetRect.Deflate(aMinTwips, aMinTwips);
+ if (r.YMost() <= insetRect.y)
+ return nsRectVisibility_kAboveViewport;
+ if (r.y >= insetRect.YMost())
+ return nsRectVisibility_kBelowViewport;
+ if (r.XMost() <= insetRect.x)
+ return nsRectVisibility_kLeftOfViewport;
+ if (r.x >= insetRect.XMost())
+ return nsRectVisibility_kRightOfViewport;
+
+ return nsRectVisibility_kVisible;
+}
+
+class PaintTimerCallBack final : public nsITimerCallback
+{
+public:
+ explicit PaintTimerCallBack(PresShell* aShell) : mShell(aShell) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) final
+ {
+ mShell->SetNextPaintCompressed();
+ mShell->AddInvalidateHiddenPresShellObserver(mShell->GetPresContext()->RefreshDriver());
+ mShell->ScheduleViewManagerFlush();
+ return NS_OK;
+ }
+
+private:
+ ~PaintTimerCallBack() {}
+
+ PresShell* mShell;
+};
+
+NS_IMPL_ISUPPORTS(PaintTimerCallBack, nsITimerCallback)
+
+void
+PresShell::ScheduleViewManagerFlush(PaintType aType)
+{
+ if (aType == PAINT_DELAYED_COMPRESS) {
+ // Delay paint for 1 second.
+ static const uint32_t kPaintDelayPeriod = 1000;
+ if (!mDelayedPaintTimer) {
+ mDelayedPaintTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ RefPtr<PaintTimerCallBack> cb = new PaintTimerCallBack(this);
+ mDelayedPaintTimer->InitWithCallback(cb, kPaintDelayPeriod, nsITimer::TYPE_ONE_SHOT);
+ }
+ return;
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->RefreshDriver()->ScheduleViewManagerFlush();
+ }
+ if (mDocument) {
+ mDocument->SetNeedLayoutFlush();
+ }
+}
+
+bool
+FlushLayoutRecursive(nsIDocument* aDocument,
+ void* aData = nullptr)
+{
+ MOZ_ASSERT(!aData);
+ nsCOMPtr<nsIDocument> kungFuDeathGrip(aDocument);
+ aDocument->EnumerateSubDocuments(FlushLayoutRecursive, nullptr);
+ aDocument->FlushPendingNotifications(Flush_Layout);
+ return true;
+}
+
+void
+PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent,
+ bool aFlushOnHoverChange)
+{
+ RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
+ uint32_t hoverGenerationBefore =
+ restyleManager->GetHoverGeneration();
+ nsEventStatus status;
+ nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
+ if (!targetView)
+ return;
+ targetView->GetViewManager()->DispatchEvent(aEvent, targetView, &status);
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return;
+ }
+ if (aFlushOnHoverChange &&
+ hoverGenerationBefore != restyleManager->GetHoverGeneration()) {
+ // Flush so that the resulting reflow happens now so that our caller
+ // can suppress any synthesized mouse moves caused by that reflow.
+ // This code only ever runs for the root document, but :hover changes
+ // can happen in descendant documents too, so make sure we flush
+ // all of them.
+ FlushLayoutRecursive(mDocument);
+ }
+}
+
+void
+PresShell::ClearMouseCaptureOnView(nsView* aView)
+{
+ if (gCaptureInfo.mContent) {
+ if (aView) {
+ // if a view was specified, ensure that the captured content is within
+ // this view.
+ nsIFrame* frame = gCaptureInfo.mContent->GetPrimaryFrame();
+ if (frame) {
+ nsView* view = frame->GetClosestView();
+ // if there is no view, capturing won't be handled any more, so
+ // just release the capture.
+ if (view) {
+ do {
+ if (view == aView) {
+ gCaptureInfo.mContent = nullptr;
+ // the view containing the captured content likely disappeared so
+ // disable capture for now.
+ gCaptureInfo.mAllowed = false;
+ break;
+ }
+
+ view = view->GetParent();
+ } while (view);
+ // return if the view wasn't found
+ return;
+ }
+ }
+ }
+
+ gCaptureInfo.mContent = nullptr;
+ }
+
+ // disable mouse capture until the next mousedown as a dialog has opened
+ // or a drag has started. Otherwise, someone could start capture during
+ // the modal dialog or drag.
+ gCaptureInfo.mAllowed = false;
+}
+
+void
+nsIPresShell::ClearMouseCapture(nsIFrame* aFrame)
+{
+ if (!gCaptureInfo.mContent) {
+ gCaptureInfo.mAllowed = false;
+ return;
+ }
+
+ // null frame argument means clear the capture
+ if (!aFrame) {
+ gCaptureInfo.mContent = nullptr;
+ gCaptureInfo.mAllowed = false;
+ return;
+ }
+
+ nsIFrame* capturingFrame = gCaptureInfo.mContent->GetPrimaryFrame();
+ if (!capturingFrame) {
+ gCaptureInfo.mContent = nullptr;
+ gCaptureInfo.mAllowed = false;
+ return;
+ }
+
+ if (nsLayoutUtils::IsAncestorFrameCrossDoc(aFrame, capturingFrame)) {
+ gCaptureInfo.mContent = nullptr;
+ gCaptureInfo.mAllowed = false;
+ }
+}
+
+nsresult
+PresShell::CaptureHistoryState(nsILayoutHistoryState** aState)
+{
+ NS_PRECONDITION(nullptr != aState, "null state pointer");
+
+ // We actually have to mess with the docshell here, since we want to
+ // store the state back in it.
+ // XXXbz this isn't really right, since this is being called in the
+ // content viewer's Hide() method... by that point the docshell's
+ // state could be wrong. We should sort out a better ownership
+ // model for the layout history state.
+ nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
+ if (!docShell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
+ if (!historyState) {
+ // Create the document state object
+ historyState = NS_NewLayoutHistoryState();
+ docShell->SetLayoutHistoryState(historyState);
+ }
+
+ *aState = historyState;
+ NS_IF_ADDREF(*aState);
+
+ // Capture frame state for the entire frame hierarchy
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) return NS_OK;
+
+ mFrameConstructor->CaptureFrameState(rootFrame, historyState);
+
+ return NS_OK;
+}
+
+void
+PresShell::ScheduleBeforeFirstPaint()
+{
+ if (!mDocument->IsResourceDoc()) {
+ // Notify observers that a new page is about to be drawn. Execute this
+ // as soon as it is safe to run JS, which is guaranteed to be before we
+ // go back to the event loop and actually draw the page.
+ nsContentUtils::AddScriptRunner(new nsBeforeFirstPaintDispatcher(mDocument));
+ }
+}
+
+void
+PresShell::UnsuppressAndInvalidate()
+{
+ // Note: We ignore the EnsureVisible check for resource documents, because
+ // they won't have a docshell, so they'll always fail EnsureVisible.
+ if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
+ mHaveShutDown) {
+ // No point; we're about to be torn down anyway.
+ return;
+ }
+
+ ScheduleBeforeFirstPaint();
+
+ mPaintingSuppressed = false;
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (rootFrame) {
+ // let's assume that outline on a root frame is not supported
+ rootFrame->InvalidateFrame();
+ }
+
+ // now that painting is unsuppressed, focus may be set on the document
+ if (nsPIDOMWindowOuter* win = mDocument->GetWindow())
+ win->SetReadyForFocus();
+
+ if (!mHaveShutDown) {
+ SynthesizeMouseMove(false);
+ ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+}
+
+void
+PresShell::UnsuppressPainting()
+{
+ if (mPaintSuppressionTimer) {
+ mPaintSuppressionTimer->Cancel();
+ mPaintSuppressionTimer = nullptr;
+ }
+
+ if (mIsDocumentGone || !mPaintingSuppressed)
+ return;
+
+ // If we have reflows pending, just wait until we process
+ // the reflows and get all the frames where we want them
+ // before actually unlocking the painting. Otherwise
+ // go ahead and unlock now.
+ if (!mDirtyRoots.IsEmpty())
+ mShouldUnsuppressPainting = true;
+ else
+ UnsuppressAndInvalidate();
+}
+
+// Post a request to handle an arbitrary callback after reflow has finished.
+nsresult
+PresShell::PostReflowCallback(nsIReflowCallback* aCallback)
+{
+ void* result = AllocateMisc(sizeof(nsCallbackEventRequest));
+ nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
+
+ request->callback = aCallback;
+ request->next = nullptr;
+
+ if (mLastCallbackEventRequest) {
+ mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
+ } else {
+ mFirstCallbackEventRequest = request;
+ mLastCallbackEventRequest = request;
+ }
+
+ return NS_OK;
+}
+
+void
+PresShell::CancelReflowCallback(nsIReflowCallback* aCallback)
+{
+ nsCallbackEventRequest* before = nullptr;
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ while(node)
+ {
+ nsIReflowCallback* callback = node->callback;
+
+ if (callback == aCallback)
+ {
+ nsCallbackEventRequest* toFree = node;
+ if (node == mFirstCallbackEventRequest) {
+ node = node->next;
+ mFirstCallbackEventRequest = node;
+ NS_ASSERTION(before == nullptr, "impossible");
+ } else {
+ node = node->next;
+ before->next = node;
+ }
+
+ if (toFree == mLastCallbackEventRequest) {
+ mLastCallbackEventRequest = before;
+ }
+
+ FreeMisc(sizeof(nsCallbackEventRequest), toFree);
+ } else {
+ before = node;
+ node = node->next;
+ }
+ }
+}
+
+void
+PresShell::CancelPostedReflowCallbacks()
+{
+ while (mFirstCallbackEventRequest) {
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ mFirstCallbackEventRequest = node->next;
+ if (!mFirstCallbackEventRequest) {
+ mLastCallbackEventRequest = nullptr;
+ }
+ nsIReflowCallback* callback = node->callback;
+ FreeMisc(sizeof(nsCallbackEventRequest), node);
+ if (callback) {
+ callback->ReflowCallbackCanceled();
+ }
+ }
+}
+
+void
+PresShell::HandlePostedReflowCallbacks(bool aInterruptible)
+{
+ bool shouldFlush = false;
+
+ while (mFirstCallbackEventRequest) {
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ mFirstCallbackEventRequest = node->next;
+ if (!mFirstCallbackEventRequest) {
+ mLastCallbackEventRequest = nullptr;
+ }
+ nsIReflowCallback* callback = node->callback;
+ FreeMisc(sizeof(nsCallbackEventRequest), node);
+ if (callback) {
+ if (callback->ReflowFinished()) {
+ shouldFlush = true;
+ }
+ }
+ }
+
+ mozFlushType flushType =
+ aInterruptible ? Flush_InterruptibleLayout : Flush_Layout;
+ if (shouldFlush && !mIsDestroying) {
+ FlushPendingNotifications(flushType);
+ }
+}
+
+bool
+PresShell::IsSafeToFlush() const
+{
+ // Not safe if we are reflowing or in the middle of frame construction
+ bool isSafeToFlush = !mIsReflowing &&
+ !mChangeNestCount;
+
+ if (isSafeToFlush) {
+ // Not safe if we are painting
+ nsViewManager* viewManager = GetViewManager();
+ if (viewManager) {
+ bool isPainting = false;
+ viewManager->IsPainting(isPainting);
+ if (isPainting) {
+ isSafeToFlush = false;
+ }
+ }
+ }
+
+ return isSafeToFlush;
+}
+
+
+void
+PresShell::FlushPendingNotifications(mozFlushType aType)
+{
+ // by default, flush animations if aType >= Flush_Style
+ mozilla::ChangesToFlush flush(aType, aType >= Flush_Style);
+ FlushPendingNotifications(flush);
+}
+
+void
+PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush)
+{
+ if (mIsZombie) {
+ return;
+ }
+
+ /**
+ * VERY IMPORTANT: If you add some sort of new flushing to this
+ * method, make sure to add the relevant SetNeedLayoutFlush or
+ * SetNeedStyleFlush calls on the document.
+ */
+ mozFlushType flushType = aFlush.mFlushType;
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ static const char flushTypeNames[][20] = {
+ "Content",
+ "ContentAndNotify",
+ "Style",
+ "InterruptibleLayout",
+ "Layout",
+ "Display"
+ };
+
+ // Make sure that we don't miss things added to mozFlushType!
+ MOZ_ASSERT(static_cast<uint32_t>(flushType) <= ArrayLength(flushTypeNames));
+
+ PROFILER_LABEL_PRINTF("PresShell", "Flush",
+ js::ProfileEntry::Category::GRAPHICS, "(Flush_%s)", flushTypeNames[flushType - 1]);
+#endif
+
+#ifdef ACCESSIBILITY
+#ifdef DEBUG
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
+ "Flush during accessible tree update!");
+ }
+#endif
+#endif
+
+ NS_ASSERTION(flushType >= Flush_Frames, "Why did we get called?");
+
+ bool isSafeToFlush = IsSafeToFlush();
+
+ // If layout could possibly trigger scripts, then it's only safe to flush if
+ // it's safe to run script.
+ bool hasHadScriptObject;
+ if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
+ hasHadScriptObject) {
+ isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
+ }
+
+ NS_ASSERTION(!isSafeToFlush || mViewManager, "Must have view manager");
+ // Make sure the view manager stays alive.
+ RefPtr<nsViewManager> viewManager = mViewManager;
+ bool didStyleFlush = false;
+ bool didLayoutFlush = false;
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip;
+ if (isSafeToFlush && viewManager) {
+ // Processing pending notifications can kill us, and some callers only
+ // hold weak refs when calling FlushPendingNotifications(). :(
+ kungFuDeathGrip = this;
+
+ if (mResizeEvent.IsPending()) {
+ FireResizeEvent();
+ if (mIsDestroying) {
+ return;
+ }
+ }
+
+ // We need to make sure external resource documents are flushed too (for
+ // example, svg filters that reference a filter in an external document
+ // need the frames in the external document to be constructed for the
+ // filter to work). We only need external resources to be flushed when the
+ // main document is flushing >= Flush_Frames, so we flush external
+ // resources here instead of nsDocument::FlushPendingNotifications.
+ mDocument->FlushExternalResources(flushType);
+
+ // Force flushing of any pending content notifications that might have
+ // queued up while our event was pending. That will ensure that we don't
+ // construct frames for content right now that's still waiting to be
+ // notified on,
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ // Process pending restyles, since any flush of the presshell wants
+ // up-to-date style data.
+ if (!mIsDestroying) {
+ viewManager->FlushDelayedResize(false);
+ mPresContext->FlushPendingMediaFeatureValuesChanged();
+
+ // Flush any pending update of the user font set, since that could
+ // cause style changes (for updating ex/ch units, and to cause a
+ // reflow).
+ mDocument->FlushUserFontSet();
+
+ mPresContext->FlushCounterStyles();
+
+ // Flush any requested SMIL samples.
+ if (mDocument->HasAnimationController()) {
+ mDocument->GetAnimationController()->FlushResampleRequests();
+ }
+
+ if (aFlush.mFlushAnimations && mPresContext->EffectCompositor()) {
+ mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
+ }
+
+ // The FlushResampleRequests() above flushed style changes.
+ if (!mIsDestroying) {
+ nsAutoScriptBlocker scriptBlocker;
+ mPresContext->RestyleManager()->ProcessPendingRestyles();
+ }
+ }
+
+ // Process whatever XBL constructors those restyles queued up. This
+ // ensures that onload doesn't fire too early and that we won't do extra
+ // reflows after those constructors run.
+ if (!mIsDestroying) {
+ mDocument->BindingManager()->ProcessAttachedQueue();
+ }
+
+ // Now those constructors or events might have posted restyle
+ // events. At the same time, we still need up-to-date style data.
+ // In particular, reflow depends on style being completely up to
+ // date. If it's not, then style context reparenting, which can
+ // happen during reflow, might suddenly pick up the new rules and
+ // we'll end up with frames whose style doesn't match the frame
+ // type.
+ if (!mIsDestroying) {
+ nsAutoScriptBlocker scriptBlocker;
+ mPresContext->RestyleManager()->ProcessPendingRestyles();
+ }
+
+ didStyleFlush = true;
+
+
+ // There might be more pending constructors now, but we're not going to
+ // worry about them. They can't be triggered during reflow, so we should
+ // be good.
+
+ if (flushType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) &&
+ !mIsDestroying) {
+ didLayoutFlush = true;
+ mFrameConstructor->RecalcQuotesAndCounters();
+ viewManager->FlushDelayedResize(true);
+ if (ProcessReflowCommands(flushType < Flush_Layout) && mContentToScrollTo) {
+ // We didn't get interrupted. Go ahead and scroll to our content
+ DoScrollContentIntoView();
+ if (mContentToScrollTo) {
+ mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ }
+ }
+ }
+
+ if (flushType >= Flush_Layout) {
+ if (!mIsDestroying) {
+ viewManager->UpdateWidgetGeometry();
+ }
+ }
+ }
+
+ if (!didStyleFlush && flushType >= Flush_Style && !mIsDestroying) {
+ mDocument->SetNeedStyleFlush();
+ }
+
+ if (!didLayoutFlush && !mIsDestroying &&
+ (flushType >=
+ (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout))) {
+ // We suppressed this flush due to mSuppressInterruptibleReflows or
+ // !isSafeToFlush, but the document thinks it doesn't
+ // need to flush anymore. Let it know what's really going on.
+ mDocument->SetNeedLayoutFlush();
+ }
+}
+
+void
+PresShell::CharacterDataChanged(nsIDocument *aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected CharacterDataChanged");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ nsIContent *container = aContent->GetParent();
+ uint32_t selectorFlags =
+ container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+ if (selectorFlags != 0 && !aContent->IsRootOfAnonymousSubtree()) {
+ Element* element = container->AsElement();
+ if (aInfo->mAppend && !aContent->GetNextSibling())
+ mPresContext->RestyleManager()->RestyleForAppend(element, aContent);
+ else
+ mPresContext->RestyleManager()->RestyleForInsertOrChange(element, aContent);
+ }
+
+ mFrameConstructor->CharacterDataChanged(aContent, aInfo);
+ VERIFY_STYLE_TREE;
+}
+
+void
+PresShell::ContentStateChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ EventStates aStateMask)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentStateChanged");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->ContentStateChanged(aContent, aStateMask);
+ VERIFY_STYLE_TREE;
+ }
+}
+
+void
+PresShell::DocumentStatesChanged(nsIDocument* aDocument,
+ EventStates aStateMask)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ nsStyleSet* styleSet = mStyleSet->GetAsGecko();
+ if (!styleSet) {
+ // XXXheycam ServoStyleSets don't support document state selectors,
+ // but these are only used in chrome documents, which we are not
+ // aiming to support yet.
+ NS_WARNING("stylo: ServoStyleSets cannot respond to document state "
+ "changes yet (only matters for chrome documents). See bug 1290285.");
+ return;
+ }
+
+ if (mDidInitialize &&
+ styleSet->HasDocumentStateDependentStyle(mDocument->GetRootElement(),
+ aStateMask)) {
+ mPresContext->RestyleManager()->PostRestyleEvent(mDocument->GetRootElement(),
+ eRestyle_Subtree,
+ nsChangeHint(0));
+ VERIFY_STYLE_TREE;
+ }
+
+ if (aStateMask.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
+ nsIFrame* root = mFrameConstructor->GetRootFrame();
+ if (root) {
+ root->SchedulePaint();
+ }
+ }
+}
+
+void
+PresShell::AttributeWillChange(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected AttributeWillChange");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ // XXXwaterson it might be more elegant to wait until after the
+ // initial reflow to begin observing the document. That would
+ // squelch any other inappropriate notifications as well.
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
+ aAttribute, aModType,
+ aNewValue);
+ VERIFY_STYLE_TREE;
+ }
+}
+
+void
+PresShell::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected AttributeChanged");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ // XXXwaterson it might be more elegant to wait until after the
+ // initial reflow to begin observing the document. That would
+ // squelch any other inappropriate notifications as well.
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->AttributeChanged(aElement, aNameSpaceID,
+ aAttribute, aModType,
+ aOldValue);
+ VERIFY_STYLE_TREE;
+ }
+}
+
+// nsIMutationObserver callbacks have this terrible API where aContainer is
+// null in the case that the container is the document (since nsIDocument is
+// not an nsIContent), and callees are supposed to figure this out and use the
+// document instead. It would be nice to fix that API to just pass a single
+// nsINode* parameter in place of the nsIDocument*, nsIContent* pair, but
+// there are quite a lot of consumers. So we fix things up locally with this
+// routine for now.
+static inline nsINode*
+RealContainer(nsIDocument* aDocument, nsIContent* aContainer, nsIContent* aContent)
+{
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT_IF(aContainer, aContainer->OwnerDoc() == aDocument);
+ MOZ_ASSERT(aContent->OwnerDoc() == aDocument);
+ MOZ_ASSERT_IF(!aContainer, aContent->GetParentNode() == aDocument);
+ if (!aContainer) {
+ return aDocument;
+ }
+ return aContainer;
+}
+
+void
+PresShell::ContentAppended(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentAppended");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+
+ // We never call ContentAppended with a document as the container, so we can
+ // assert that we have an nsIContent container.
+ MOZ_ASSERT(aContainer);
+ MOZ_ASSERT(aContainer->IsElement() ||
+ aContainer->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT));
+ if (!mDidInitialize) {
+ return;
+ }
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ mPresContext->RestyleManager()->ContentAppended(aContainer, aFirstNewContent);
+
+ mFrameConstructor->ContentAppended(aContainer, aFirstNewContent, true);
+
+ VERIFY_STYLE_TREE;
+}
+
+void
+PresShell::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aMaybeContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentInserted");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+ nsINode* container = RealContainer(aDocument, aMaybeContainer, aChild);
+
+ if (!mDidInitialize) {
+ return;
+ }
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ mPresContext->RestyleManager()->ContentInserted(container, aChild);
+
+ mFrameConstructor->ContentInserted(aMaybeContainer, aChild, nullptr, true);
+
+ if (aChild->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
+ MOZ_ASSERT(container == aDocument);
+ NotifyFontSizeInflationEnabledIsDirty();
+ }
+
+ VERIFY_STYLE_TREE;
+}
+
+void
+PresShell::ContentRemoved(nsIDocument *aDocument,
+ nsIContent* aMaybeContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ NS_PRECONDITION(!mIsDocumentGone, "Unexpected ContentRemoved");
+ NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+ nsINode* container = RealContainer(aDocument, aMaybeContainer, aChild);
+
+ // Notify the ESM that the content has been removed, so that
+ // it can clean up any state related to the content.
+
+ // XXX_jwir3: There is no null check for aDocument necessary, since, even
+ // though by nsIMutationObserver, aDocument could be null, the
+ // precondition check that mDocument == aDocument ensures that
+ // aDocument will not be null (since mDocument can't be null unless
+ // we're still intializing).
+ mPresContext->EventStateManager()->ContentRemoved(aDocument, aChild);
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ nsIContent* oldNextSibling = container->GetChildAt(aIndexInContainer);
+
+ mPresContext->RestyleManager()->ContentRemoved(container, aChild, oldNextSibling);
+
+ // After removing aChild from tree we should save information about live ancestor
+ if (mPointerEventTarget) {
+ if (nsContentUtils::ContentIsDescendantOf(mPointerEventTarget, aChild)) {
+ mPointerEventTarget = aMaybeContainer;
+ }
+ }
+
+ // We should check that aChild does not contain pointer capturing elements.
+ // If it does we should release the pointer capture for the elements.
+ for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) {
+ nsIPresShell::PointerCaptureInfo* data = iter.UserData();
+ if (data && data->mPendingContent &&
+ nsContentUtils::ContentIsDescendantOf(data->mPendingContent, aChild)) {
+ nsIPresShell::ReleasePointerCapturingContent(iter.Key());
+ }
+ }
+
+ bool didReconstruct;
+ mFrameConstructor->ContentRemoved(aMaybeContainer, aChild, oldNextSibling,
+ nsCSSFrameConstructor::REMOVE_CONTENT,
+ &didReconstruct);
+
+
+ if (aChild->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
+ MOZ_ASSERT(container == aDocument);
+ NotifyFontSizeInflationEnabledIsDirty();
+ }
+
+ VERIFY_STYLE_TREE;
+}
+
+void
+PresShell::NotifyCounterStylesAreDirty()
+{
+ nsAutoCauseReflowNotifier reflowNotifier(this);
+ mFrameConstructor->BeginUpdate();
+ mFrameConstructor->NotifyCounterStylesAreDirty();
+ mFrameConstructor->EndUpdate();
+}
+
+nsresult
+PresShell::ReconstructFrames(void)
+{
+ NS_PRECONDITION(!mFrameConstructor->GetRootFrame() || mDidInitialize,
+ "Must not have root frame before initial reflow");
+ if (!mDidInitialize || mIsDestroying) {
+ // Nothing to do here
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+
+ // Have to make sure that the content notifications are flushed before we
+ // start messing with the frame model; otherwise we can get content doubling.
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ if (mIsDestroying) {
+ return NS_OK;
+ }
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mFrameConstructor->BeginUpdate();
+ nsresult rv = mFrameConstructor->ReconstructDocElementHierarchy();
+ VERIFY_STYLE_TREE;
+ mFrameConstructor->EndUpdate();
+
+ return rv;
+}
+
+void
+nsIPresShell::RestyleForCSSRuleChanges()
+{
+ AutoTArray<RefPtr<mozilla::dom::Element>,1> scopeRoots;
+ mChangedScopeStyleRoots.SwapElements(scopeRoots);
+
+ if (mStylesHaveChanged) {
+ // If we need to restyle everything, no need to restyle individual
+ // scoped style roots.
+ scopeRoots.Clear();
+ }
+
+ mStylesHaveChanged = false;
+
+ if (mIsDestroying) {
+ // We don't want to mess with restyles at this point
+ return;
+ }
+
+ mDocument->RebuildUserFontSet();
+
+ if (mPresContext) {
+ mPresContext->RebuildCounterStyles();
+ }
+
+ Element* root = mDocument->GetRootElement();
+ if (!mDidInitialize) {
+ // Nothing to do here, since we have no frames yet
+ return;
+ }
+
+ if (!root) {
+ // No content to restyle
+ return;
+ }
+
+ RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
+ if (scopeRoots.IsEmpty()) {
+ // If scopeRoots is empty, we know that mStylesHaveChanged was true at
+ // the beginning of this function, and that we need to restyle the whole
+ // document.
+ restyleManager->PostRestyleEvent(root, eRestyle_Subtree,
+ nsChangeHint(0));
+ } else {
+ for (Element* scopeRoot : scopeRoots) {
+ restyleManager->PostRestyleEvent(scopeRoot, eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ }
+}
+
+void
+PresShell::RecordStyleSheetChange(StyleSheet* aStyleSheet)
+{
+ // too bad we can't check that the update is UPDATE_STYLE
+ NS_ASSERTION(mUpdateCount != 0, "must be in an update");
+
+ if (mStylesHaveChanged)
+ return;
+
+ if (aStyleSheet->IsGecko()) {
+ // XXXheycam ServoStyleSheets don't support <style scoped> yet.
+ Element* scopeElement = aStyleSheet->AsGecko()->GetScopeElement();
+ if (scopeElement) {
+ mChangedScopeStyleRoots.AppendElement(scopeElement);
+ return;
+ }
+ } else {
+ NS_WARNING("stylo: ServoStyleSheets don't support <style scoped>");
+ return;
+ }
+
+ mStylesHaveChanged = true;
+}
+
+void
+PresShell::StyleSheetAdded(StyleSheet* aStyleSheet,
+ bool aDocumentSheet)
+{
+ // We only care when enabled sheets are added
+ NS_PRECONDITION(aStyleSheet, "Must have a style sheet!");
+
+ if (aStyleSheet->IsApplicable() && aStyleSheet->HasRules()) {
+ RecordStyleSheetChange(aStyleSheet);
+ }
+}
+
+void
+PresShell::StyleSheetRemoved(StyleSheet* aStyleSheet,
+ bool aDocumentSheet)
+{
+ // We only care when enabled sheets are removed
+ NS_PRECONDITION(aStyleSheet, "Must have a style sheet!");
+
+ if (aStyleSheet->IsApplicable() && aStyleSheet->HasRules()) {
+ RecordStyleSheetChange(aStyleSheet);
+ }
+}
+
+void
+PresShell::StyleSheetApplicableStateChanged(StyleSheet* aStyleSheet)
+{
+ if (aStyleSheet->HasRules()) {
+ RecordStyleSheetChange(aStyleSheet);
+ }
+}
+
+void
+PresShell::StyleRuleChanged(StyleSheet* aStyleSheet)
+{
+ RecordStyleSheetChange(aStyleSheet);
+}
+
+void
+PresShell::StyleRuleAdded(StyleSheet* aStyleSheet)
+{
+ RecordStyleSheetChange(aStyleSheet);
+}
+
+void
+PresShell::StyleRuleRemoved(StyleSheet* aStyleSheet)
+{
+ RecordStyleSheetChange(aStyleSheet);
+}
+
+nsIFrame*
+PresShell::GetPlaceholderFrameFor(nsIFrame* aFrame) const
+{
+ return mFrameConstructor->GetPlaceholderFrameFor(aFrame);
+}
+
+nsresult
+PresShell::RenderDocument(const nsRect& aRect, uint32_t aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aThebesContext)
+{
+ NS_ENSURE_TRUE(!(aFlags & RENDER_IS_UNTRUSTED), NS_ERROR_NOT_IMPLEMENTED);
+
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext) {
+ rootPresContext->FlushWillPaintObservers();
+ if (mIsDestroying)
+ return NS_OK;
+ }
+
+ nsAutoScriptBlocker blockScripts;
+
+ // Set up the rectangle as the path in aThebesContext
+ gfxRect r(0, 0,
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
+ aThebesContext->NewPath();
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ aThebesContext->Rectangle(r, true);
+#else
+ aThebesContext->Rectangle(r);
+#endif
+
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) {
+ // Nothing to paint, just fill the rect
+ aThebesContext->SetColor(Color::FromABGR(aBackgroundColor));
+ aThebesContext->Fill();
+ return NS_OK;
+ }
+
+ gfxContextAutoSaveRestore save(aThebesContext);
+
+ MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
+
+ aThebesContext->Clip();
+
+ nsDeviceContext* devCtx = mPresContext->DeviceContext();
+
+ gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
+ -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
+ gfxFloat scale = gfxFloat(devCtx->AppUnitsPerDevPixel())/nsPresContext::AppUnitsPerCSSPixel();
+
+ // Since canvas APIs use floats to set up their matrices, we may have some
+ // slight rounding errors here. We use NudgeToIntegers() here to adjust
+ // matrix components that are integers up to the accuracy of floats to be
+ // those integers.
+ gfxMatrix newTM = aThebesContext->CurrentMatrix().Translate(offset).
+ Scale(scale, scale).
+ NudgeToIntegers();
+ aThebesContext->SetMatrix(newTM);
+
+ AutoSaveRestoreRenderingState _(this);
+
+ nsRenderingContext rc(aThebesContext);
+
+ bool wouldFlushRetainedLayers = false;
+ PaintFrameFlags flags = PaintFrameFlags::PAINT_IGNORE_SUPPRESSION;
+ if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
+ flags |= PaintFrameFlags::PAINT_IN_TRANSFORM;
+ }
+ if (!(aFlags & RENDER_ASYNC_DECODE_IMAGES)) {
+ flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES;
+ }
+ if (aFlags & RENDER_USE_WIDGET_LAYERS) {
+ // We only support using widget layers on display root's with widgets.
+ nsView* view = rootFrame->GetView();
+ if (view && view->GetWidget() &&
+ nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
+ LayerManager* layerManager = view->GetWidget()->GetLayerManager();
+ // ClientLayerManagers in content processes don't support
+ // taking snapshots.
+ if (layerManager &&
+ (!layerManager->AsClientLayerManager() ||
+ XRE_IsParentProcess())) {
+ flags |= PaintFrameFlags::PAINT_WIDGET_LAYERS;
+ }
+ }
+ }
+ if (!(aFlags & RENDER_CARET)) {
+ wouldFlushRetainedLayers = true;
+ flags |= PaintFrameFlags::PAINT_HIDE_CARET;
+ }
+ if (aFlags & RENDER_IGNORE_VIEWPORT_SCROLLING) {
+ wouldFlushRetainedLayers = !IgnoringViewportScrolling();
+ mRenderFlags = ChangeFlag(mRenderFlags, true, STATE_IGNORING_VIEWPORT_SCROLLING);
+ }
+ if (aFlags & RENDER_DRAWWINDOW_NOT_FLUSHING) {
+ mRenderFlags = ChangeFlag(mRenderFlags, true, STATE_DRAWWINDOW_NOT_FLUSHING);
+ }
+ if (aFlags & RENDER_DOCUMENT_RELATIVE) {
+ // XXX be smarter about this ... drawWindow might want a rect
+ // that's "pretty close" to what our retained layer tree covers.
+ // In that case, it wouldn't disturb normal rendering too much,
+ // and we should allow it.
+ wouldFlushRetainedLayers = true;
+ flags |= PaintFrameFlags::PAINT_DOCUMENT_RELATIVE;
+ }
+
+ // Don't let drawWindow blow away our retained layer tree
+ if ((flags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && wouldFlushRetainedLayers) {
+ flags &= ~PaintFrameFlags::PAINT_WIDGET_LAYERS;
+ }
+
+ nsLayoutUtils::PaintFrame(&rc, rootFrame, nsRegion(aRect),
+ aBackgroundColor,
+ nsDisplayListBuilderMode::PAINTING,
+ flags);
+
+ return NS_OK;
+}
+
+/*
+ * Clip the display list aList to a range. Returns the clipped
+ * rectangle surrounding the range.
+ */
+nsRect
+PresShell::ClipListToRange(nsDisplayListBuilder *aBuilder,
+ nsDisplayList* aList,
+ nsRange* aRange)
+{
+ // iterate though the display items and add up the bounding boxes of each.
+ // This will allow the total area of the frames within the range to be
+ // determined. To do this, remove an item from the bottom of the list, check
+ // whether it should be part of the range, and if so, append it to the top
+ // of the temporary list tmpList. If the item is a text frame at the end of
+ // the selection range, clip it to the portion of the text frame that is
+ // part of the selection. Then, append the wrapper to the top of the list.
+ // Otherwise, just delete the item and don't append it.
+ nsRect surfaceRect;
+ nsDisplayList tmpList;
+
+ nsDisplayItem* i;
+ while ((i = aList->RemoveBottom())) {
+ // itemToInsert indiciates the item that should be inserted into the
+ // temporary list. If null, no item should be inserted.
+ nsDisplayItem* itemToInsert = nullptr;
+ nsIFrame* frame = i->Frame();
+ nsIContent* content = frame->GetContent();
+ if (content) {
+ bool atStart = (content == aRange->GetStartParent());
+ bool atEnd = (content == aRange->GetEndParent());
+ if ((atStart || atEnd) && frame->GetType() == nsGkAtoms::textFrame) {
+ int32_t frameStartOffset, frameEndOffset;
+ frame->GetOffsets(frameStartOffset, frameEndOffset);
+
+ int32_t hilightStart =
+ atStart ? std::max(aRange->StartOffset(), frameStartOffset) : frameStartOffset;
+ int32_t hilightEnd =
+ atEnd ? std::min(aRange->EndOffset(), frameEndOffset) : frameEndOffset;
+ if (hilightStart < hilightEnd) {
+ // determine the location of the start and end edges of the range.
+ nsPoint startPoint, endPoint;
+ frame->GetPointFromOffset(hilightStart, &startPoint);
+ frame->GetPointFromOffset(hilightEnd, &endPoint);
+
+ // The clip rectangle is determined by taking the the start and
+ // end points of the range, offset from the reference frame.
+ // Because of rtl, the end point may be to the left of (or above,
+ // in vertical mode) the start point, so x (or y) is set to the
+ // lower of the values.
+ nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
+ if (frame->GetWritingMode().IsVertical()) {
+ nscoord y = std::min(startPoint.y, endPoint.y);
+ textRect.y += y;
+ textRect.height = std::max(startPoint.y, endPoint.y) - y;
+ } else {
+ nscoord x = std::min(startPoint.x, endPoint.x);
+ textRect.x += x;
+ textRect.width = std::max(startPoint.x, endPoint.x) - x;
+ }
+ surfaceRect.UnionRect(surfaceRect, textRect);
+
+ DisplayItemClip newClip;
+ newClip.SetTo(textRect);
+ newClip.IntersectWith(i->GetClip());
+ i->SetClip(aBuilder, newClip);
+ itemToInsert = i;
+ }
+ }
+ // Don't try to descend into subdocuments.
+ // If this ever changes we'd need to add handling for subdocuments with
+ // different zoom levels.
+ else if (content->GetUncomposedDoc() ==
+ aRange->GetStartParent()->GetUncomposedDoc()) {
+ // if the node is within the range, append it to the temporary list
+ bool before, after;
+ nsresult rv =
+ nsRange::CompareNodeToRange(content, aRange, &before, &after);
+ if (NS_SUCCEEDED(rv) && !before && !after) {
+ itemToInsert = i;
+ bool snap;
+ surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
+ }
+ }
+ }
+
+ // insert the item into the list if necessary. If the item has a child
+ // list, insert that as well
+ nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
+ if (itemToInsert || sublist) {
+ tmpList.AppendToTop(itemToInsert ? itemToInsert : i);
+ // if the item is a list, iterate over it as well
+ if (sublist)
+ surfaceRect.UnionRect(surfaceRect,
+ ClipListToRange(aBuilder, sublist, aRange));
+ }
+ else {
+ // otherwise, just delete the item and don't readd it to the list
+ i->~nsDisplayItem();
+ }
+ }
+
+ // now add all the items back onto the original list again
+ aList->AppendToTop(&tmpList);
+
+ return surfaceRect;
+}
+
+#ifdef DEBUG
+#include <stdio.h>
+
+static bool gDumpRangePaintList = false;
+#endif
+
+UniquePtr<RangePaintInfo>
+PresShell::CreateRangePaintInfo(nsIDOMRange* aRange,
+ nsRect& aSurfaceRect,
+ bool aForPrimarySelection)
+{
+ nsRange* range = static_cast<nsRange*>(aRange);
+ nsIFrame* ancestorFrame;
+ nsIFrame* rootFrame = GetRootFrame();
+
+ // If the start or end of the range is the document, just use the root
+ // frame, otherwise get the common ancestor of the two endpoints of the
+ // range.
+ nsINode* startParent = range->GetStartParent();
+ nsINode* endParent = range->GetEndParent();
+ nsIDocument* doc = startParent->GetComposedDoc();
+ if (startParent == doc || endParent == doc) {
+ ancestorFrame = rootFrame;
+ } else {
+ nsINode* ancestor = nsContentUtils::GetCommonAncestor(startParent, endParent);
+ NS_ASSERTION(!ancestor || ancestor->IsNodeOfType(nsINode::eCONTENT),
+ "common ancestor is not content");
+ if (!ancestor || !ancestor->IsNodeOfType(nsINode::eCONTENT))
+ return nullptr;
+
+ nsIContent* ancestorContent = static_cast<nsIContent*>(ancestor);
+ ancestorFrame = ancestorContent->GetPrimaryFrame();
+
+ // XXX deal with ancestorFrame being null due to display:contents
+
+ // use the nearest ancestor frame that includes all continuations as the
+ // root for building the display list
+ while (ancestorFrame &&
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
+ ancestorFrame = ancestorFrame->GetParent();
+ }
+
+ if (!ancestorFrame) {
+ return nullptr;
+ }
+
+ // get a display list containing the range
+ auto info = MakeUnique<RangePaintInfo>(range, ancestorFrame);
+ info->mBuilder.SetIncludeAllOutOfFlows();
+ if (aForPrimarySelection) {
+ info->mBuilder.SetSelectedFramesOnly();
+ }
+ info->mBuilder.EnterPresShell(ancestorFrame);
+
+ nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
+ nsresult rv = iter->Init(range);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ auto BuildDisplayListForNode = [&] (nsINode* aNode) {
+ if (MOZ_UNLIKELY(!aNode->IsContent())) {
+ return;
+ }
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ // XXX deal with frame being null due to display:contents
+ for (; frame; frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
+ frame->BuildDisplayListForStackingContext(&info->mBuilder,
+ frame->GetVisualOverflowRect(), &info->mList);
+ }
+ };
+ if (startParent->NodeType() == nsIDOMNode::TEXT_NODE) {
+ BuildDisplayListForNode(startParent);
+ }
+ for (; !iter->IsDone(); iter->Next()) {
+ nsCOMPtr<nsINode> node = iter->GetCurrentNode();
+ BuildDisplayListForNode(node);
+ }
+ if (endParent != startParent &&
+ endParent->NodeType() == nsIDOMNode::TEXT_NODE) {
+ BuildDisplayListForNode(endParent);
+ }
+
+#ifdef DEBUG
+ if (gDumpRangePaintList) {
+ fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
+ nsFrame::PrintDisplayList(&(info->mBuilder), info->mList);
+ }
+#endif
+
+ nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, range);
+
+ info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);
+
+#ifdef DEBUG
+ if (gDumpRangePaintList) {
+ fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
+ nsFrame::PrintDisplayList(&(info->mBuilder), info->mList);
+ }
+#endif
+
+ // determine the offset of the reference frame for the display list
+ // to the root frame. This will allow the coordinates used when painting
+ // to all be offset from the same point
+ info->mRootOffset = ancestorFrame->GetOffsetTo(rootFrame);
+ rangeRect.MoveBy(info->mRootOffset);
+ aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
+
+ return info;
+}
+
+already_AddRefed<SourceSurface>
+PresShell::PaintRangePaintInfo(const nsTArray<UniquePtr<RangePaintInfo>>& aItems,
+ nsISelection* aSelection,
+ nsIntRegion* aRegion,
+ nsRect aArea,
+ const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags)
+{
+ nsPresContext* pc = GetPresContext();
+ if (!pc || aArea.width == 0 || aArea.height == 0)
+ return nullptr;
+
+ // use the rectangle to create the surface
+ nsIntRect pixelArea = aArea.ToOutsidePixels(pc->AppUnitsPerDevPixel());
+
+ // if the image should not be resized, the scale, relative to the original image, must be 1
+ float scale = 1.0;
+ nsIntRect rootScreenRect =
+ GetRootFrame()->GetScreenRectInAppUnits().ToNearestPixels(
+ pc->AppUnitsPerDevPixel());
+
+ nsRect maxSize;
+ pc->DeviceContext()->GetClientRect(maxSize);
+
+ // check if the image should be resized
+ bool resize = aFlags & RENDER_AUTO_SCALE;
+
+ if (resize) {
+ // check if image-resizing-algorithm should be used
+ if (aFlags & RENDER_IS_IMAGE) {
+ // get max screensize
+ nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
+ nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
+ // resize image relative to the screensize
+ // get best height/width relative to screensize
+ float bestHeight = float(maxHeight)*RELATIVE_SCALEFACTOR;
+ float bestWidth = float(maxWidth)*RELATIVE_SCALEFACTOR;
+ // get scalefactor to reach bestWidth
+ scale = bestWidth / float(pixelArea.width);
+ // get the worst height (height when width is perfect)
+ float worstHeight = float(pixelArea.height)*scale;
+ // get the difference of best and worst height
+ float difference = bestHeight - worstHeight;
+ // half the difference and add it to worstHeight,
+ // then get scalefactor to reach this
+ scale = (worstHeight + difference / 2) / float(pixelArea.height);
+ } else {
+ // get half of max screensize
+ nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
+ nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
+ if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
+ scale = 1.0;
+ // divide the maximum size by the image size in both directions. Whichever
+ // direction produces the smallest result determines how much should be
+ // scaled.
+ if (pixelArea.width > maxWidth)
+ scale = std::min(scale, float(maxWidth) / pixelArea.width);
+ if (pixelArea.height > maxHeight)
+ scale = std::min(scale, float(maxHeight) / pixelArea.height);
+ }
+ }
+
+
+ pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
+ pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
+ if (!pixelArea.width || !pixelArea.height)
+ return nullptr;
+
+ // adjust the screen position based on the rescaled size
+ nscoord left = rootScreenRect.x + pixelArea.x;
+ nscoord top = rootScreenRect.y + pixelArea.y;
+ aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x - left) * scale);
+ aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y - top) * scale);
+ }
+ else {
+ // move aScreenRect to the position of the surface in screen coordinates
+ aScreenRect->MoveTo(rootScreenRect.x + pixelArea.x, rootScreenRect.y + pixelArea.y);
+ }
+ aScreenRect->width = pixelArea.width;
+ aScreenRect->height = pixelArea.height;
+
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ IntSize(pixelArea.width, pixelArea.height),
+ SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(ctx); // already checked the draw target above
+
+ if (aRegion) {
+ RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
+
+ // Convert aRegion from CSS pixels to dev pixels
+ nsIntRegion region =
+ aRegion->ToAppUnits(nsPresContext::AppUnitsPerCSSPixel())
+ .ToOutsidePixels(pc->AppUnitsPerDevPixel());
+ for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
+ const nsIntRect& rect = iter.Get();
+
+ builder->MoveTo(rect.TopLeft());
+ builder->LineTo(rect.TopRight());
+ builder->LineTo(rect.BottomRight());
+ builder->LineTo(rect.BottomLeft());
+ builder->LineTo(rect.TopLeft());
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ ctx->Clip(path);
+ }
+
+ nsRenderingContext rc(ctx);
+
+ gfxMatrix initialTM = ctx->CurrentMatrix();
+
+ if (resize)
+ initialTM.Scale(scale, scale);
+
+ // translate so that points are relative to the surface area
+ gfxPoint surfaceOffset =
+ nsLayoutUtils::PointToGfxPoint(-aArea.TopLeft(), pc->AppUnitsPerDevPixel());
+ initialTM.Translate(surfaceOffset);
+
+ // temporarily hide the selection so that text is drawn normally. If a
+ // selection is being rendered, use that, otherwise use the presshell's
+ // selection.
+ RefPtr<nsFrameSelection> frameSelection;
+ if (aSelection) {
+ frameSelection = aSelection->AsSelection()->GetFrameSelection();
+ }
+ else {
+ frameSelection = FrameSelection();
+ }
+ int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
+ frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
+
+ // next, paint each range in the selection
+ for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
+ // the display lists paint relative to the offset from the reference
+ // frame, so account for that translation too:
+ gfxPoint rootOffset =
+ nsLayoutUtils::PointToGfxPoint(rangeInfo->mRootOffset,
+ pc->AppUnitsPerDevPixel());
+ ctx->SetMatrix(gfxMatrix(initialTM).Translate(rootOffset));
+ aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
+ nsRegion visible(aArea);
+ RefPtr<LayerManager> layerManager =
+ rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &rc,
+ nsDisplayList::PAINT_DEFAULT);
+ aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
+ }
+
+ // restore the old selection display state
+ frameSelection->SetDisplaySelection(oldDisplaySelection);
+
+ return dt->Snapshot();
+}
+
+already_AddRefed<SourceSurface>
+PresShell::RenderNode(nsIDOMNode* aNode,
+ nsIntRegion* aRegion,
+ const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags)
+{
+ // area will hold the size of the surface needed to draw the node, measured
+ // from the root frame.
+ nsRect area;
+ nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
+
+ // nothing to draw if the node isn't in a document
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ if (!node->IsInUncomposedDoc())
+ return nullptr;
+
+ RefPtr<nsRange> range = new nsRange(node);
+ if (NS_FAILED(range->SelectNode(aNode)))
+ return nullptr;
+
+ UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
+ if (info && !rangeItems.AppendElement(Move(info))) {
+ return nullptr;
+ }
+
+ if (aRegion) {
+ // combine the area with the supplied region
+ nsIntRect rrectPixels = aRegion->GetBounds();
+
+ nsRect rrect = ToAppUnits(rrectPixels, nsPresContext::AppUnitsPerCSSPixel());
+ area.IntersectRect(area, rrect);
+
+ nsPresContext* pc = GetPresContext();
+ if (!pc)
+ return nullptr;
+
+ // move the region so that it is offset from the topleft corner of the surface
+ aRegion->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
+ -nsPresContext::AppUnitsToIntCSSPixels(area.y));
+ }
+
+ return PaintRangePaintInfo(rangeItems, nullptr, aRegion, area, aPoint,
+ aScreenRect, aFlags);
+}
+
+already_AddRefed<SourceSurface>
+PresShell::RenderSelection(nsISelection* aSelection,
+ const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags)
+{
+ // area will hold the size of the surface needed to draw the selection,
+ // measured from the root frame.
+ nsRect area;
+ nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
+
+ // iterate over each range and collect them into the rangeItems array.
+ // This is done so that the size of selection can be determined so as
+ // to allocate a surface area
+ int32_t numRanges;
+ aSelection->GetRangeCount(&numRanges);
+ NS_ASSERTION(numRanges > 0, "RenderSelection called with no selection");
+
+ for (int32_t r = 0; r < numRanges; r++)
+ {
+ nsCOMPtr<nsIDOMRange> range;
+ aSelection->GetRangeAt(r, getter_AddRefs(range));
+
+ UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
+ if (info && !rangeItems.AppendElement(Move(info))) {
+ return nullptr;
+ }
+ }
+
+ return PaintRangePaintInfo(rangeItems, aSelection, nullptr, area, aPoint,
+ aScreenRect, aFlags);
+}
+
+void
+PresShell::AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds)
+{
+ aList.AppendNewToBottom(new (&aBuilder)
+ nsDisplaySolidColor(&aBuilder, aFrame, aBounds, NS_RGB(115, 115, 115)));
+}
+
+static bool
+AddCanvasBackgroundColor(const nsDisplayList& aList, nsIFrame* aCanvasFrame,
+ nscolor aColor, bool aCSSBackgroundColor)
+{
+ for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
+ if (i->Frame() == aCanvasFrame &&
+ i->GetType() == nsDisplayItem::TYPE_CANVAS_BACKGROUND_COLOR) {
+ nsDisplayCanvasBackgroundColor* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
+ bg->SetExtraBackgroundColor(aColor);
+ return true;
+ }
+ nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
+ if (sublist &&
+ !(i->GetType() == nsDisplayItem::TYPE_BLEND_CONTAINER && !aCSSBackgroundColor) &&
+ AddCanvasBackgroundColor(*sublist, aCanvasFrame, aColor, aCSSBackgroundColor))
+ return true;
+ }
+ return false;
+}
+
+void
+PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds,
+ nscolor aBackstopColor,
+ uint32_t aFlags)
+{
+ if (aBounds.IsEmpty()) {
+ return;
+ }
+ // We don't want to add an item for the canvas background color if the frame
+ // (sub)tree we are painting doesn't include any canvas frames. There isn't
+ // an easy way to check this directly, but if we check if the root of the
+ // (sub)tree we are painting is a canvas frame that should cover us in all
+ // cases (it will usually be a viewport frame when we have a canvas frame in
+ // the (sub)tree).
+ if (!(aFlags & nsIPresShell::FORCE_DRAW) &&
+ !nsCSSRendering::IsCanvasFrame(aFrame)) {
+ return;
+ }
+
+ nscolor bgcolor = NS_ComposeColors(aBackstopColor, mCanvasBackgroundColor);
+ if (NS_GET_A(bgcolor) == 0)
+ return;
+
+ // To make layers work better, we want to avoid having a big non-scrolled
+ // color background behind a scrolled transparent background. Instead,
+ // we'll try to move the color background into the scrolled content
+ // by making nsDisplayCanvasBackground paint it.
+ if (!aFrame->GetParent()) {
+ nsIScrollableFrame* sf =
+ aFrame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+ if (canvasFrame && canvasFrame->IsVisibleForPainting(&aBuilder)) {
+ if (AddCanvasBackgroundColor(aList, canvasFrame, bgcolor, mHasCSSBackgroundColor))
+ return;
+ }
+ }
+ }
+
+ aList.AppendNewToBottom(
+ new (&aBuilder) nsDisplaySolidColor(&aBuilder, aFrame, aBounds, bgcolor));
+}
+
+static bool IsTransparentContainerElement(nsPresContext* aPresContext)
+{
+ nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> pwin = docShell->GetWindow();
+ if (!pwin)
+ return false;
+ nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal();
+
+ TabChild* tab = TabChild::GetFrom(docShell);
+ if (tab) {
+ // Check if presShell is the top PresShell. Only the top can
+ // influence the canvas background color.
+ nsCOMPtr<nsIPresShell> presShell = aPresContext->GetPresShell();
+ nsCOMPtr<nsIPresShell> topPresShell = tab->GetPresShell();
+ if (presShell != topPresShell) {
+ tab = nullptr;
+ }
+ }
+
+ return (containerElement &&
+ containerElement->HasAttr(kNameSpaceID_None, nsGkAtoms::transparent))
+ || (tab && tab->IsTransparent());
+}
+
+nscolor PresShell::GetDefaultBackgroundColorToDraw()
+{
+ if (!mPresContext || !mPresContext->GetBackgroundColorDraw()) {
+ return NS_RGB(255,255,255);
+ }
+ return mPresContext->DefaultBackgroundColor();
+}
+
+void PresShell::UpdateCanvasBackground()
+{
+ // If we have a frame tree and it has style information that
+ // specifies the background color of the canvas, update our local
+ // cache of that color.
+ nsIFrame* rootStyleFrame = FrameConstructor()->GetRootElementStyleFrame();
+ if (rootStyleFrame) {
+ nsStyleContext* bgStyle =
+ nsCSSRendering::FindRootFrameBackground(rootStyleFrame);
+ // XXX We should really be passing the canvasframe, not the root element
+ // style frame but we don't have access to the canvasframe here. It isn't
+ // a problem because only a few frames can return something other than true
+ // and none of them would be a canvas frame or root element style frame.
+ bool drawBackgroundImage;
+ bool drawBackgroundColor;
+ mCanvasBackgroundColor =
+ nsCSSRendering::DetermineBackgroundColor(mPresContext, bgStyle,
+ rootStyleFrame,
+ drawBackgroundImage,
+ drawBackgroundColor);
+ mHasCSSBackgroundColor = drawBackgroundColor;
+ if (mPresContext->IsRootContentDocument() &&
+ !IsTransparentContainerElement(mPresContext)) {
+ mCanvasBackgroundColor =
+ NS_ComposeColors(GetDefaultBackgroundColorToDraw(), mCanvasBackgroundColor);
+ }
+ }
+
+ // If the root element of the document (ie html) has style 'display: none'
+ // then the document's background color does not get drawn; cache the
+ // color we actually draw.
+ if (!FrameConstructor()->GetRootElementFrame()) {
+ mCanvasBackgroundColor = GetDefaultBackgroundColorToDraw();
+ }
+}
+
+nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot)
+{
+ nsIWidget* widget = aDisplayRoot->GetWidget();
+ if (widget && (widget->GetTransparencyMode() != eTransparencyOpaque ||
+ widget->WidgetPaintsBackground())) {
+ // Within a transparent widget, so the backstop color must be
+ // totally transparent.
+ return NS_RGBA(0,0,0,0);
+ }
+ // Within an opaque widget (or no widget at all), so the backstop
+ // color must be totally opaque. The user's default background
+ // as reported by the prescontext is guaranteed to be opaque.
+ return GetDefaultBackgroundColorToDraw();
+}
+
+struct PaintParams {
+ nscolor mBackgroundColor;
+};
+
+LayerManager* PresShell::GetLayerManager()
+{
+ NS_ASSERTION(mViewManager, "Should have view manager");
+
+ nsView* rootView = mViewManager->GetRootView();
+ if (rootView) {
+ if (nsIWidget* widget = rootView->GetWidget()) {
+ return widget->GetLayerManager();
+ }
+ }
+ return nullptr;
+}
+
+bool PresShell::AsyncPanZoomEnabled()
+{
+ NS_ASSERTION(mViewManager, "Should have view manager");
+ nsView* rootView = mViewManager->GetRootView();
+ if (rootView) {
+ if (nsIWidget* widget = rootView->GetWidget()) {
+ return widget->AsyncPanZoomEnabled();
+ }
+ }
+ return gfxPlatform::AsyncPanZoomEnabled();
+}
+
+void PresShell::SetIgnoreViewportScrolling(bool aIgnore)
+{
+ if (IgnoringViewportScrolling() == aIgnore) {
+ return;
+ }
+ RenderingState state(this);
+ state.mRenderFlags = ChangeFlag(state.mRenderFlags, aIgnore,
+ STATE_IGNORING_VIEWPORT_SCROLLING);
+ SetRenderingState(state);
+}
+
+nsresult PresShell::SetResolutionImpl(float aResolution, bool aScaleToResolution)
+{
+ if (!(aResolution > 0.0)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aResolution == mResolution.valueOr(0.0)) {
+ MOZ_ASSERT(mResolution.isSome());
+ return NS_OK;
+ }
+ RenderingState state(this);
+ state.mResolution = Some(aResolution);
+ SetRenderingState(state);
+ mScaleToResolution = aScaleToResolution;
+ if (mMobileViewportManager) {
+ mMobileViewportManager->ResolutionUpdated();
+ }
+
+ return NS_OK;
+}
+
+bool PresShell::ScaleToResolution() const
+{
+ return mScaleToResolution;
+}
+
+float PresShell::GetCumulativeResolution()
+{
+ float resolution = GetResolution();
+ nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
+ if (parentCtx) {
+ resolution *= parentCtx->PresShell()->GetCumulativeResolution();
+ }
+ return resolution;
+}
+
+float PresShell::GetCumulativeNonRootScaleResolution()
+{
+ float resolution = 1.0;
+ nsIPresShell* currentShell = this;
+ while (currentShell) {
+ nsPresContext* currentCtx = currentShell->GetPresContext();
+ if (currentCtx != currentCtx->GetRootPresContext()) {
+ resolution *= currentShell->ScaleToResolution() ? currentShell->GetResolution() : 1.0f;
+ }
+ nsPresContext* parentCtx = currentCtx->GetParentPresContext();
+ if (parentCtx) {
+ currentShell = parentCtx->PresShell();
+ } else {
+ currentShell = nullptr;
+ }
+ }
+ return resolution;
+}
+
+void PresShell::SetRestoreResolution(float aResolution,
+ LayoutDeviceIntSize aDisplaySize)
+{
+ if (mMobileViewportManager) {
+ mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
+ }
+}
+
+void PresShell::SetRenderingState(const RenderingState& aState)
+{
+ if (mRenderFlags != aState.mRenderFlags) {
+ // Rendering state changed in a way that forces us to flush any
+ // retained layers we might already have.
+ LayerManager* manager = GetLayerManager();
+ if (manager) {
+ FrameLayerBuilder::InvalidateAllLayers(manager);
+ }
+ }
+
+ mRenderFlags = aState.mRenderFlags;
+ mResolution = aState.mResolution;
+}
+
+void PresShell::SynthesizeMouseMove(bool aFromScroll)
+{
+ if (!sSynthMouseMove)
+ return;
+
+ if (mPaintingSuppressed || !mIsActive || !mPresContext) {
+ return;
+ }
+
+ if (!mPresContext->IsRoot()) {
+ nsIPresShell* rootPresShell = GetRootPresShell();
+ if (rootPresShell) {
+ rootPresShell->SynthesizeMouseMove(aFromScroll);
+ }
+ return;
+ }
+
+ if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
+ return;
+
+ if (!mSynthMouseMoveEvent.IsPending()) {
+ RefPtr<nsSynthMouseMoveEvent> ev =
+ new nsSynthMouseMoveEvent(this, aFromScroll);
+
+ if (!GetPresContext()->RefreshDriver()->AddRefreshObserver(ev,
+ Flush_Display)) {
+ NS_WARNING("failed to dispatch nsSynthMouseMoveEvent");
+ return;
+ }
+
+ mSynthMouseMoveEvent = ev;
+ }
+}
+
+/**
+ * Find the first floating view with a widget in a postorder traversal of the
+ * view tree that contains the point. Thus more deeply nested floating views
+ * are preferred over their ancestors, and floating views earlier in the
+ * view hierarchy (i.e., added later) are preferred over their siblings.
+ * This is adequate for finding the "topmost" floating view under a point,
+ * given that floating views don't supporting having a specific z-index.
+ *
+ * We cannot exit early when aPt is outside the view bounds, because floating
+ * views aren't necessarily included in their parent's bounds, so this could
+ * traverse the entire view hierarchy --- use carefully.
+ */
+static nsView* FindFloatingViewContaining(nsView* aView, nsPoint aPt)
+{
+ if (aView->GetVisibility() == nsViewVisibility_kHide)
+ // No need to look into descendants.
+ return nullptr;
+
+ nsIFrame* frame = aView->GetFrame();
+ if (frame) {
+ if (!frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
+ !frame->PresContext()->PresShell()->IsActive()) {
+ return nullptr;
+ }
+ }
+
+ for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
+ nsView* r = FindFloatingViewContaining(v, v->ConvertFromParentCoords(aPt));
+ if (r)
+ return r;
+ }
+
+ if (aView->GetFloating() && aView->HasWidget() &&
+ aView->GetDimensions().Contains(aPt))
+ return aView;
+
+ return nullptr;
+}
+
+/*
+ * This finds the first view containing the given point in a postorder
+ * traversal of the view tree that contains the point, assuming that the
+ * point is not in a floating view. It assumes that only floating views
+ * extend outside the bounds of their parents.
+ *
+ * This methods should only be called if FindFloatingViewContaining
+ * returns null.
+ */
+static nsView* FindViewContaining(nsView* aView, nsPoint aPt)
+{
+ if (!aView->GetDimensions().Contains(aPt) ||
+ aView->GetVisibility() == nsViewVisibility_kHide) {
+ return nullptr;
+ }
+
+ nsIFrame* frame = aView->GetFrame();
+ if (frame) {
+ if (!frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
+ !frame->PresContext()->PresShell()->IsActive()) {
+ return nullptr;
+ }
+ }
+
+ for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
+ nsView* r = FindViewContaining(v, v->ConvertFromParentCoords(aPt));
+ if (r)
+ return r;
+ }
+
+ return aView;
+}
+
+void
+PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll)
+{
+ // If drag session has started, we shouldn't synthesize mousemove event.
+ nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
+ if (dragSession) {
+ mSynthMouseMoveEvent.Forget();
+ return;
+ }
+
+ // allow new event to be posted while handling this one only if the
+ // source of the event is a scroll (to prevent infinite reflow loops)
+ if (aFromScroll) {
+ mSynthMouseMoveEvent.Forget();
+ }
+
+ nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
+ if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
+ !rootView || !rootView->HasWidget() || !mPresContext) {
+ mSynthMouseMoveEvent.Forget();
+ return;
+ }
+
+ NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");
+
+ // Hold a ref to ourselves so DispatchEvent won't destroy us (since
+ // we need to access members after we call DispatchEvent).
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+
+#ifdef DEBUG_MOUSE_LOCATION
+ printf("[ps=%p]synthesizing mouse move to (%d,%d)\n",
+ this, mMouseLocation.x, mMouseLocation.y);
+#endif
+
+ int32_t APD = mPresContext->AppUnitsPerDevPixel();
+
+ // We need a widget to put in the event we are going to dispatch so we look
+ // for a view that has a widget and the mouse location is over. We first look
+ // for floating views, if there isn't one we use the root view. |view| holds
+ // that view.
+ nsView* view = nullptr;
+
+ // The appunits per devpixel ratio of |view|.
+ int32_t viewAPD;
+
+ // mRefPoint will be mMouseLocation relative to the widget of |view|, the
+ // widget we will put in the event we dispatch, in viewAPD appunits
+ nsPoint refpoint(0, 0);
+
+ // We always dispatch the event to the pres shell that contains the view that
+ // the mouse is over. pointVM is the VM of that pres shell.
+ nsViewManager *pointVM = nullptr;
+
+ // This could be a bit slow (traverses entire view hierarchy)
+ // but it's OK to do it once per synthetic mouse event
+ view = FindFloatingViewContaining(rootView, mMouseLocation);
+ if (!view) {
+ view = rootView;
+ nsView *pointView = FindViewContaining(rootView, mMouseLocation);
+ // pointView can be null in situations related to mouse capture
+ pointVM = (pointView ? pointView : view)->GetViewManager();
+ refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
+ viewAPD = APD;
+ } else {
+ pointVM = view->GetViewManager();
+ nsIFrame* frame = view->GetFrame();
+ NS_ASSERTION(frame, "floating views can't be anonymous");
+ viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
+ refpoint = mMouseLocation.ScaleToOtherAppUnits(APD, viewAPD);
+ refpoint -= view->GetOffsetTo(rootView);
+ refpoint += view->ViewToWidgetOffset();
+ }
+ NS_ASSERTION(view->GetWidget(), "view should have a widget here");
+ WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
+ WidgetMouseEvent::eSynthesized);
+ event.mRefPoint =
+ LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
+ event.mTime = PR_IntervalNow();
+ // XXX set event.mModifiers ?
+ // XXX mnakano I think that we should get the latest information from widget.
+
+ nsCOMPtr<nsIPresShell> shell = pointVM->GetPresShell();
+ if (shell) {
+ // Since this gets run in a refresh tick there isn't an InputAPZContext on
+ // the stack from the nsBaseWidget. We need to simulate one with at least
+ // the correct target guid, so that the correct callback transform gets
+ // applied if this event goes to a child process. The input block id is set
+ // to 0 because this is a synthetic event which doesn't really belong to any
+ // input block. Same for the APZ response field.
+ InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
+ shell->DispatchSynthMouseMove(&event, !aFromScroll);
+ }
+
+ if (!aFromScroll) {
+ mSynthMouseMoveEvent.Forget();
+ }
+}
+
+static void
+AddFrameToVisibleRegions(nsIFrame* aFrame,
+ nsViewManager* aViewManager,
+ Maybe<VisibleRegions>& aVisibleRegions)
+{
+ if (!aVisibleRegions) {
+ return;
+ }
+
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aViewManager);
+
+ // Retrieve the view ID for this frame (which we obtain from the enclosing
+ // scrollable frame).
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(aFrame,
+ nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
+ nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT);
+ if (!scrollableFrame) {
+ return;
+ }
+
+ nsIFrame* scrollableFrameAsFrame = do_QueryFrame(scrollableFrame);
+ MOZ_ASSERT(scrollableFrameAsFrame);
+
+ nsIContent* scrollableFrameContent = scrollableFrameAsFrame->GetContent();
+ if (!scrollableFrameContent) {
+ return;
+ }
+
+ ViewID viewID;
+ if (!nsLayoutUtils::FindIDFor(scrollableFrameContent, &viewID)) {
+ return ;
+ }
+
+ // Update the visible region for the appropriate view ID.
+ nsRect frameRectInScrolledFrameSpace = aFrame->GetVisualOverflowRect();
+ nsLayoutUtils::TransformResult result =
+ nsLayoutUtils::TransformRect(aFrame,
+ scrollableFrame->GetScrolledFrame(),
+ frameRectInScrolledFrameSpace);
+ if (result != nsLayoutUtils::TransformResult::TRANSFORM_SUCCEEDED) {
+ return;
+ }
+
+ CSSIntRegion* regionForView = aVisibleRegions->LookupOrAdd(viewID);
+ MOZ_ASSERT(regionForView);
+
+ regionForView->OrWith(CSSPixel::FromAppUnitsRounded(frameRectInScrolledFrameSpace));
+}
+
+/* static */ void
+PresShell::MarkFramesInListApproximatelyVisible(const nsDisplayList& aList,
+ Maybe<VisibleRegions>& aVisibleRegions)
+{
+ for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
+ nsDisplayList* sublist = item->GetChildren();
+ if (sublist) {
+ MarkFramesInListApproximatelyVisible(*sublist, aVisibleRegions);
+ continue;
+ }
+
+ nsIFrame* frame = item->Frame();
+ MOZ_ASSERT(frame);
+
+ if (!frame->TrackingVisibility()) {
+ continue;
+ }
+
+ // Use the presshell containing the frame.
+ auto* presShell = static_cast<PresShell*>(frame->PresContext()->PresShell());
+ uint32_t count = presShell->mApproximatelyVisibleFrames.Count();
+ MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
+ presShell->mApproximatelyVisibleFrames.PutEntry(frame);
+ if (presShell->mApproximatelyVisibleFrames.Count() > count) {
+ // The frame was added to mApproximatelyVisibleFrames, so increment its visible count.
+ frame->IncApproximateVisibleCount();
+ }
+
+ AddFrameToVisibleRegions(frame, presShell->mViewManager, aVisibleRegions);
+ }
+}
+
+static void
+NotifyCompositorOfVisibleRegionsChange(PresShell* aPresShell,
+ const Maybe<VisibleRegions>& aRegions)
+{
+ if (!aRegions) {
+ return;
+ }
+
+ MOZ_ASSERT(aPresShell);
+
+ // Retrieve the layers ID and pres shell ID.
+ TabChild* tabChild = TabChild::GetFrom(aPresShell);
+ if (!tabChild) {
+ return;
+ }
+
+ const uint64_t layersId = tabChild->LayersId();
+ const uint32_t presShellId = aPresShell->GetPresShellId();
+
+ // Retrieve the CompositorBridgeChild.
+ LayerManager* layerManager = aPresShell->GetLayerManager();
+ if (!layerManager) {
+ return;
+ }
+
+ ClientLayerManager* clientLayerManager = layerManager->AsClientLayerManager();
+ if (!clientLayerManager) {
+ return;
+ }
+
+ CompositorBridgeChild* compositorChild = clientLayerManager->GetCompositorBridgeChild();
+ if (!compositorChild) {
+ return;
+ }
+
+ // Clear the old approximately visible regions associated with this document.
+ compositorChild->SendClearApproximatelyVisibleRegions(layersId, presShellId);
+
+ // Send the new approximately visible regions to the compositor.
+ for (auto iter = aRegions->ConstIter(); !iter.Done(); iter.Next()) {
+ const ViewID viewId = iter.Key();
+ const CSSIntRegion* region = iter.UserData();
+ MOZ_ASSERT(region);
+
+ const ScrollableLayerGuid guid(layersId, presShellId, viewId);
+
+ compositorChild->SendNotifyApproximatelyVisibleRegion(guid, *region);
+ }
+}
+
+/* static */ void
+PresShell::DecApproximateVisibleCount(VisibleFrames& aFrames,
+ Maybe<OnNonvisible> aNonvisibleAction
+ /* = Nothing() */)
+{
+ for (auto iter = aFrames.Iter(); !iter.Done(); iter.Next()) {
+ nsIFrame* frame = iter.Get()->GetKey();
+ // Decrement the frame's visible count if we're still tracking its
+ // visibility. (We may not be, if the frame disabled visibility tracking
+ // after we added it to the visible frames list.)
+ if (frame->TrackingVisibility()) {
+ frame->DecApproximateVisibleCount(aNonvisibleAction);
+ }
+ }
+}
+
+void
+PresShell::RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList)
+{
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
+ mApproximateFrameVisibilityVisited = true;
+
+ // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
+ // them in oldApproxVisibleFrames.
+ VisibleFrames oldApproximatelyVisibleFrames;
+ mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);
+
+ // If we're visualizing visible regions, create a VisibleRegions object to
+ // store information about them. The functions we call will populate this
+ // object and send it to the compositor only if it's Some(), so we don't
+ // need to check the prefs everywhere.
+ Maybe<VisibleRegions> visibleRegions;
+ if (gfxPrefs::APZMinimap() && gfxPrefs::APZMinimapVisibilityEnabled()) {
+ visibleRegions.emplace();
+ }
+
+ MarkFramesInListApproximatelyVisible(aList, visibleRegions);
+
+ DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
+
+ NotifyCompositorOfVisibleRegionsChange(this, visibleRegions);
+}
+
+/* static */ void
+PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear)
+{
+ nsViewManager* vm = aView->GetViewManager();
+ if (aClear) {
+ PresShell* presShell = static_cast<PresShell*>(vm->GetPresShell());
+ if (!presShell->mApproximateFrameVisibilityVisited) {
+ presShell->ClearApproximatelyVisibleFramesList();
+ }
+ presShell->mApproximateFrameVisibilityVisited = false;
+ }
+ for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
+ ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
+ }
+}
+
+void
+PresShell::ClearApproximatelyVisibleFramesList(Maybe<OnNonvisible> aNonvisibleAction
+ /* = Nothing() */)
+{
+ DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
+ mApproximatelyVisibleFrames.Clear();
+}
+
+void
+PresShell::MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame,
+ const nsRect& aRect,
+ Maybe<VisibleRegions>& aVisibleRegions,
+ bool aRemoveOnly /* = false */)
+{
+ MOZ_ASSERT(aFrame->PresContext()->PresShell() == this, "wrong presshell");
+
+ if (aFrame->TrackingVisibility() &&
+ aFrame->StyleVisibility()->IsVisible() &&
+ (!aRemoveOnly || aFrame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE)) {
+ MOZ_ASSERT(!AssumeAllFramesVisible());
+ uint32_t count = mApproximatelyVisibleFrames.Count();
+ mApproximatelyVisibleFrames.PutEntry(aFrame);
+ if (mApproximatelyVisibleFrames.Count() > count) {
+ // The frame was added to mApproximatelyVisibleFrames, so increment its visible count.
+ aFrame->IncApproximateVisibleCount();
+ }
+
+ AddFrameToVisibleRegions(aFrame, mViewManager, aVisibleRegions);
+ }
+
+ nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
+ if (subdocFrame) {
+ nsIPresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
+ nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
+ if (presShell && !presShell->AssumeAllFramesVisible()) {
+ nsRect rect = aRect;
+ nsIFrame* root = presShell->GetRootFrame();
+ if (root) {
+ rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
+ } else {
+ rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
+ }
+ rect = rect.ScaleToOtherAppUnitsRoundOut(
+ aFrame->PresContext()->AppUnitsPerDevPixel(),
+ presShell->GetPresContext()->AppUnitsPerDevPixel());
+
+ presShell->RebuildApproximateFrameVisibility(&rect);
+ }
+ return;
+ }
+
+ nsRect rect = aRect;
+
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
+ if (scrollFrame) {
+ scrollFrame->NotifyApproximateFrameVisibilityUpdate();
+ nsRect displayPort;
+ bool usingDisplayport =
+ nsLayoutUtils::GetDisplayPortForVisibilityTesting(
+ aFrame->GetContent(), &displayPort, RelativeTo::ScrollFrame);
+ if (usingDisplayport) {
+ rect = displayPort;
+ } else {
+ rect = rect.Intersect(scrollFrame->GetScrollPortRect());
+ }
+ rect = scrollFrame->ExpandRectToNearlyVisible(rect);
+ }
+
+ bool preserves3DChildren = aFrame->Extend3DContext();
+
+ // We assume all frames in popups are visible, so we skip them here.
+ const nsIFrame::ChildListIDs skip(nsIFrame::kPopupList |
+ nsIFrame::kSelectPopupList);
+ for (nsIFrame::ChildListIterator childLists(aFrame);
+ !childLists.IsDone(); childLists.Next()) {
+ if (skip.Contains(childLists.CurrentID())) {
+ continue;
+ }
+
+ for (nsIFrame* child : childLists.CurrentList()) {
+ nsRect r = rect - child->GetPosition();
+ if (!r.IntersectRect(r, child->GetVisualOverflowRect())) {
+ continue;
+ }
+ if (child->IsTransformed()) {
+ // for children of a preserve3d element we just pass down the same dirty rect
+ if (!preserves3DChildren || !child->Combines3DTransformWithAncestors()) {
+ const nsRect overflow = child->GetVisualOverflowRectRelativeToSelf();
+ nsRect out;
+ if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
+ r = out;
+ } else {
+ r.SetEmpty();
+ }
+ }
+ }
+ MarkFramesInSubtreeApproximatelyVisible(child, r, aVisibleRegions);
+ }
+ }
+}
+
+void
+PresShell::RebuildApproximateFrameVisibility(nsRect* aRect,
+ bool aRemoveOnly /* = false */)
+{
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
+ mApproximateFrameVisibilityVisited = true;
+
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
+ // them in oldApproximatelyVisibleFrames.
+ VisibleFrames oldApproximatelyVisibleFrames;
+ mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);
+
+ // If we're visualizing visible regions, create a VisibleRegions object to
+ // store information about them. The functions we call will populate this
+ // object and send it to the compositor only if it's Some(), so we don't
+ // need to check the prefs everywhere.
+ Maybe<VisibleRegions> visibleRegions;
+ if (gfxPrefs::APZMinimap() && gfxPrefs::APZMinimapVisibilityEnabled()) {
+ visibleRegions.emplace();
+ }
+
+ nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
+ if (aRect) {
+ vis = *aRect;
+ }
+
+ MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, visibleRegions, aRemoveOnly);
+
+ DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
+
+ NotifyCompositorOfVisibleRegionsChange(this, visibleRegions);
+}
+
+void
+PresShell::UpdateApproximateFrameVisibility()
+{
+ DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
+}
+
+void
+PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly)
+{
+ MOZ_ASSERT(!mPresContext || mPresContext->IsRootContentDocument(),
+ "Updating approximate frame visibility on a non-root content document?");
+
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ if (mHaveShutDown || mIsDestroying) {
+ return;
+ }
+
+ // call update on that frame
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES));
+ return;
+ }
+
+ RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
+ ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+
+#ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
+ // This can be used to debug the frame walker by comparing beforeFrameList
+ // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see if
+ // they produce the same results (mApproximatelyVisibleFrames holds the frames the
+ // display list thinks are visible, beforeFrameList holds the frames the
+ // frame walker thinks are visible).
+ nsDisplayListBuilder builder(rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
+ nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
+ nsIFrame* rootScroll = GetRootScrollFrame();
+ if (rootScroll) {
+ nsIContent* content = rootScroll->GetContent();
+ if (content) {
+ Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(content, &updateRect,
+ RelativeTo::ScrollFrame);
+ }
+
+ if (IgnoringViewportScrolling()) {
+ builder.SetIgnoreScrollFrame(rootScroll);
+ }
+ }
+ builder.IgnorePaintSuppression();
+ builder.EnterPresShell(rootFrame);
+ nsDisplayList list;
+ rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
+ builder.LeavePresShell(rootFrame, &list);
+
+ RebuildApproximateFrameVisibilityDisplayList(list);
+
+ ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+
+ list.DeleteAll();
+#endif
+}
+
+bool
+PresShell::AssumeAllFramesVisible()
+{
+ static bool sFrameVisibilityEnabled = true;
+ static bool sFrameVisibilityPrefCached = false;
+
+ if (!sFrameVisibilityPrefCached) {
+ Preferences::AddBoolVarCache(&sFrameVisibilityEnabled,
+ "layout.framevisibility.enabled", true);
+ sFrameVisibilityPrefCached = true;
+ }
+
+ if (!sFrameVisibilityEnabled || !mPresContext || !mDocument) {
+ return true;
+ }
+
+ // We assume all frames are visible in print, print preview, chrome, and
+ // resource docs and don't keep track of them.
+ if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
+ mPresContext->Type() == nsPresContext::eContext_Print ||
+ mPresContext->IsChrome() ||
+ mDocument->IsResourceDoc()) {
+ return true;
+ }
+
+ // If we're assuming all frames are visible in the top level content
+ // document, we need to in subdocuments as well. Otherwise we can get in a
+ // situation where things like animations won't work in subdocuments because
+ // their frames appear not to be visible, since we won't schedule an image
+ // visibility update if the top level content document is assuming all
+ // frames are visible.
+ //
+ // Note that it's not safe to call IsRootContentDocument() if we're
+ // currently being destroyed, so we have to check that first.
+ if (!mHaveShutDown && !mIsDestroying &&
+ !mPresContext->IsRootContentDocument()) {
+ nsPresContext* presContext =
+ mPresContext->GetToplevelContentDocumentPresContext();
+ if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+PresShell::ScheduleApproximateFrameVisibilityUpdateSoon()
+{
+ if (AssumeAllFramesVisible()) {
+ return;
+ }
+
+ if (!mPresContext) {
+ return;
+ }
+
+ nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
+ if (!refreshDriver) {
+ return;
+ }
+
+ // Ask the refresh driver to update frame visibility soon.
+ refreshDriver->ScheduleFrameVisibilityUpdate();
+}
+
+void
+PresShell::ScheduleApproximateFrameVisibilityUpdateNow()
+{
+ if (AssumeAllFramesVisible()) {
+ return;
+ }
+
+ if (!mPresContext->IsRootContentDocument()) {
+ nsPresContext* presContext = mPresContext->GetToplevelContentDocumentPresContext();
+ if (!presContext)
+ return;
+ MOZ_ASSERT(presContext->IsRootContentDocument(),
+ "Didn't get a root prescontext from GetToplevelContentDocumentPresContext?");
+ presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
+ return;
+ }
+
+ if (mHaveShutDown || mIsDestroying) {
+ return;
+ }
+
+ if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
+ return;
+ }
+
+ RefPtr<nsRunnableMethod<PresShell> > ev =
+ NewRunnableMethod(this, &PresShell::UpdateApproximateFrameVisibility);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mUpdateApproximateFrameVisibilityEvent = ev;
+ }
+}
+
+void
+PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame)
+{
+ if (!aFrame->TrackingVisibility()) {
+ return;
+ }
+
+ if (AssumeAllFramesVisible()) {
+ aFrame->IncApproximateVisibleCount();
+ return;
+ }
+
+#ifdef DEBUG
+ // Make sure it's in this pres shell.
+ nsCOMPtr<nsIContent> content = aFrame->GetContent();
+ if (content) {
+ PresShell* shell = static_cast<PresShell*>(content->OwnerDoc()->GetShell());
+ MOZ_ASSERT(!shell || shell == this, "wrong shell");
+ }
+#endif
+
+ if (!mApproximatelyVisibleFrames.Contains(aFrame)) {
+ MOZ_ASSERT(!AssumeAllFramesVisible());
+ mApproximatelyVisibleFrames.PutEntry(aFrame);
+ aFrame->IncApproximateVisibleCount();
+ }
+}
+
+void
+PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame)
+{
+#ifdef DEBUG
+ // Make sure it's in this pres shell.
+ nsCOMPtr<nsIContent> content = aFrame->GetContent();
+ if (content) {
+ PresShell* shell = static_cast<PresShell*>(content->OwnerDoc()->GetShell());
+ MOZ_ASSERT(!shell || shell == this, "wrong shell");
+ }
+#endif
+
+ if (AssumeAllFramesVisible()) {
+ MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
+ "Shouldn't have any frames in the table");
+ return;
+ }
+
+ uint32_t count = mApproximatelyVisibleFrames.Count();
+ mApproximatelyVisibleFrames.RemoveEntry(aFrame);
+
+ if (aFrame->TrackingVisibility() &&
+ mApproximatelyVisibleFrames.Count() < count) {
+ // aFrame was in the hashtable, and we're still tracking its visibility,
+ // so we need to decrement its visible count.
+ aFrame->DecApproximateVisibleCount();
+ }
+}
+
+class nsAutoNotifyDidPaint
+{
+public:
+ nsAutoNotifyDidPaint(PresShell* aShell, uint32_t aFlags)
+ : mShell(aShell), mFlags(aFlags)
+ {
+ }
+ ~nsAutoNotifyDidPaint()
+ {
+ mShell->GetPresContext()->NotifyDidPaintForSubtree(mFlags);
+ }
+
+private:
+ PresShell* mShell;
+ uint32_t mFlags;
+};
+
+void
+PresShell::RecordShadowStyleChange(ShadowRoot* aShadowRoot)
+{
+ mChangedScopeStyleRoots.AppendElement(aShadowRoot->GetHost()->AsElement());
+}
+
+void
+PresShell::Paint(nsView* aViewToPaint,
+ const nsRegion& aDirtyRegion,
+ uint32_t aFlags)
+{
+ PROFILER_LABEL("PresShell", "Paint",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
+ NS_ASSERTION(aViewToPaint, "null view");
+
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
+
+ if (!mIsActive || mIsZombie) {
+ return;
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
+
+ nsIFrame* frame = aViewToPaint->GetFrame();
+
+ LayerManager* layerManager =
+ aViewToPaint->GetWidget()->GetLayerManager();
+ NS_ASSERTION(layerManager, "Must be in paint event");
+ bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
+
+ nsAutoNotifyDidPaint notifyDidPaint(this, aFlags);
+
+ // Whether or not we should set first paint when painting is
+ // suppressed is debatable. For now we'll do it because
+ // B2G relies on first paint to configure the viewport and
+ // we only want to do that when we have real content to paint.
+ // See Bug 798245
+ if (mIsFirstPaint && !mPaintingSuppressed) {
+ layerManager->SetIsFirstPaint();
+ mIsFirstPaint = false;
+ }
+
+ if (!layerManager->BeginTransaction()) {
+ return;
+ }
+
+ if (frame) {
+ // Try to do an empty transaction, if the frame tree does not
+ // need to be updated. Do not try to do an empty transaction on
+ // a non-retained layer manager (like the BasicLayerManager that
+ // draws the window title bar on Mac), because a) it won't work
+ // and b) below we don't want to clear NS_FRAME_UPDATE_LAYER_TREE,
+ // that will cause us to forget to update the real layer manager!
+
+ if (!(aFlags & PAINT_LAYERS)) {
+ if (layerManager->EndEmptyTransaction()) {
+ return;
+ }
+ NS_WARNING("Must complete empty transaction when compositing!");
+ }
+
+ if (!(aFlags & PAINT_SYNC_DECODE_IMAGES) &&
+ !(frame->GetStateBits() & NS_FRAME_UPDATE_LAYER_TREE) &&
+ !mNextPaintCompressed) {
+ NotifySubDocInvalidationFunc computeInvalidFunc =
+ presContext->MayHavePaintEventListenerInSubDocument() ? nsPresContext::NotifySubDocInvalidation : 0;
+ bool computeInvalidRect = computeInvalidFunc ||
+ (layerManager->GetBackendType() == LayersBackend::LAYERS_BASIC);
+
+ UniquePtr<LayerProperties> props;
+ if (computeInvalidRect) {
+ props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
+ }
+
+ MaybeSetupTransactionIdAllocator(layerManager, aViewToPaint);
+
+ if (layerManager->EndEmptyTransaction((aFlags & PAINT_COMPOSITE) ?
+ LayerManager::END_DEFAULT : LayerManager::END_NO_COMPOSITE)) {
+ nsIntRegion invalid;
+ if (props) {
+ invalid = props->ComputeDifferences(layerManager->GetRoot(), computeInvalidFunc);
+ } else {
+ LayerProperties::ClearInvalidations(layerManager->GetRoot());
+ }
+ if (props) {
+ if (!invalid.IsEmpty()) {
+ nsIntRect bounds = invalid.GetBounds();
+ nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
+ presContext->DevPixelsToAppUnits(bounds.y),
+ presContext->DevPixelsToAppUnits(bounds.width),
+ presContext->DevPixelsToAppUnits(bounds.height));
+ if (shouldInvalidate) {
+ aViewToPaint->GetViewManager()->InvalidateViewNoSuppression(aViewToPaint, rect);
+ }
+ presContext->NotifyInvalidation(bounds, 0);
+ }
+ } else if (shouldInvalidate) {
+ aViewToPaint->GetViewManager()->InvalidateView(aViewToPaint);
+ }
+
+ frame->UpdatePaintCountForPaintedPresShells();
+ return;
+ }
+ }
+ frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
+ }
+ if (frame) {
+ frame->ClearPresShellsFromLastPaint();
+ }
+
+ nscolor bgcolor = ComputeBackstopColor(aViewToPaint);
+ PaintFrameFlags flags = PaintFrameFlags::PAINT_WIDGET_LAYERS |
+ PaintFrameFlags::PAINT_EXISTING_TRANSACTION;
+ if (!(aFlags & PAINT_COMPOSITE)) {
+ flags |= PaintFrameFlags::PAINT_NO_COMPOSITE;
+ }
+ if (aFlags & PAINT_SYNC_DECODE_IMAGES) {
+ flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES;
+ }
+ if (mNextPaintCompressed) {
+ flags |= PaintFrameFlags::PAINT_COMPRESSED;
+ mNextPaintCompressed = false;
+ }
+
+ if (frame) {
+ // We can paint directly into the widget using its layer manager.
+ nsLayoutUtils::PaintFrame(nullptr, frame, aDirtyRegion, bgcolor,
+ nsDisplayListBuilderMode::PAINTING, flags);
+ return;
+ }
+
+ RefPtr<ColorLayer> root = layerManager->CreateColorLayer();
+ if (root) {
+ nsPresContext* pc = GetPresContext();
+ nsIntRect bounds =
+ pc->GetVisibleArea().ToOutsidePixels(pc->AppUnitsPerDevPixel());
+ bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
+ root->SetColor(Color::FromABGR(bgcolor));
+ root->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(bounds));
+ layerManager->SetRoot(root);
+ }
+ MaybeSetupTransactionIdAllocator(layerManager, aViewToPaint);
+ layerManager->EndTransaction(nullptr, nullptr, (aFlags & PAINT_COMPOSITE) ?
+ LayerManager::END_DEFAULT : LayerManager::END_NO_COMPOSITE);
+}
+
+// static
+void
+nsIPresShell::SetCapturingContent(nsIContent* aContent, uint8_t aFlags)
+{
+ // If capture was set for pointer lock, don't unlock unless we are coming
+ // out of pointer lock explicitly.
+ if (!aContent && gCaptureInfo.mPointerLock &&
+ !(aFlags & CAPTURE_POINTERLOCK)) {
+ return;
+ }
+
+ gCaptureInfo.mContent = nullptr;
+
+ // only set capturing content if allowed or the CAPTURE_IGNOREALLOWED or
+ // CAPTURE_POINTERLOCK flags are used.
+ if ((aFlags & CAPTURE_IGNOREALLOWED) || gCaptureInfo.mAllowed ||
+ (aFlags & CAPTURE_POINTERLOCK)) {
+ if (aContent) {
+ gCaptureInfo.mContent = aContent;
+ }
+ // CAPTURE_POINTERLOCK is the same as CAPTURE_RETARGETTOELEMENT & CAPTURE_IGNOREALLOWED
+ gCaptureInfo.mRetargetToElement = ((aFlags & CAPTURE_RETARGETTOELEMENT) != 0) ||
+ ((aFlags & CAPTURE_POINTERLOCK) != 0);
+ gCaptureInfo.mPreventDrag = (aFlags & CAPTURE_PREVENTDRAG) != 0;
+ gCaptureInfo.mPointerLock = (aFlags & CAPTURE_POINTERLOCK) != 0;
+ }
+}
+
+/* static */ void
+nsIPresShell::SetPointerCapturingContent(uint32_t aPointerId,
+ nsIContent* aContent)
+{
+ MOZ_ASSERT(aContent != nullptr);
+
+ if (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == GetPointerType(aPointerId)) {
+ SetCapturingContent(aContent, CAPTURE_PREVENTDRAG);
+ }
+
+ PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
+ if (pointerCaptureInfo) {
+ pointerCaptureInfo->mPendingContent = aContent;
+ } else {
+ sPointerCaptureList->Put(aPointerId, new PointerCaptureInfo(aContent));
+ }
+}
+
+/* static */ nsIPresShell::PointerCaptureInfo*
+nsIPresShell::GetPointerCaptureInfo(uint32_t aPointerId)
+{
+ PointerCaptureInfo* pointerCaptureInfo = nullptr;
+ sPointerCaptureList->Get(aPointerId, &pointerCaptureInfo);
+ return pointerCaptureInfo;
+}
+
+/* static */ void
+nsIPresShell::ReleasePointerCapturingContent(uint32_t aPointerId)
+{
+ if (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == GetPointerType(aPointerId)) {
+ SetCapturingContent(nullptr, CAPTURE_PREVENTDRAG);
+ }
+
+ PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
+ if (pointerCaptureInfo) {
+ pointerCaptureInfo->mPendingContent = nullptr;
+ }
+}
+
+/* static */ nsIContent*
+nsIPresShell::GetPointerCapturingContent(uint32_t aPointerId)
+{
+ PointerCaptureInfo* pointerCaptureInfo = GetPointerCaptureInfo(aPointerId);
+ if (pointerCaptureInfo) {
+ return pointerCaptureInfo->mOverrideContent;
+ }
+ return nullptr;
+}
+
+/* static */ void
+nsIPresShell::CheckPointerCaptureState(uint32_t aPointerId,
+ uint16_t aPointerType, bool aIsPrimary)
+{
+ PointerCaptureInfo* captureInfo = GetPointerCaptureInfo(aPointerId);
+ if (captureInfo &&
+ captureInfo->mPendingContent != captureInfo->mOverrideContent) {
+ // cache captureInfo->mPendingContent since it may be changed in the pointer
+ // event listener
+ nsIContent* pendingContent = captureInfo->mPendingContent.get();
+ if (captureInfo->mOverrideContent) {
+ DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ false,
+ aPointerId, aPointerType, aIsPrimary,
+ captureInfo->mOverrideContent);
+ }
+ if (pendingContent) {
+ DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ true, aPointerId,
+ aPointerType, aIsPrimary,
+ pendingContent);
+ }
+ captureInfo->mOverrideContent = pendingContent;
+ if (captureInfo->Empty()) {
+ sPointerCaptureList->Remove(aPointerId);
+ }
+ }
+}
+
+/* static */ uint16_t
+nsIPresShell::GetPointerType(uint32_t aPointerId)
+{
+ PointerInfo* pointerInfo = nullptr;
+ if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
+ return pointerInfo->mPointerType;
+ }
+ return nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+}
+
+/* static */ bool
+nsIPresShell::GetPointerPrimaryState(uint32_t aPointerId)
+{
+ PointerInfo* pointerInfo = nullptr;
+ if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
+ return pointerInfo->mPrimaryState;
+ }
+ return false;
+}
+
+/* static */ bool
+nsIPresShell::GetPointerInfo(uint32_t aPointerId, bool& aActiveState)
+{
+ PointerInfo* pointerInfo = nullptr;
+ if (sActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) {
+ aActiveState = pointerInfo->mActiveState;
+ return true;
+ }
+ return false;
+}
+
+void
+PresShell::UpdateActivePointerState(WidgetGUIEvent* aEvent)
+{
+ switch (aEvent->mMessage) {
+ case eMouseEnterIntoWidget:
+ // In this case we have to know information about available mouse pointers
+ if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
+ sActivePointersIds->Put(mouseEvent->pointerId,
+ new PointerInfo(false, mouseEvent->inputSource,
+ true));
+ }
+ break;
+ case ePointerDown:
+ // In this case we switch pointer to active state
+ if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
+ sActivePointersIds->Put(pointerEvent->pointerId,
+ new PointerInfo(true, pointerEvent->inputSource,
+ pointerEvent->mIsPrimary));
+ }
+ break;
+ case ePointerUp:
+ // In this case we remove information about pointer or turn off active state
+ if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
+ if(pointerEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+ sActivePointersIds->Put(pointerEvent->pointerId,
+ new PointerInfo(false,
+ pointerEvent->inputSource,
+ pointerEvent->mIsPrimary));
+ } else {
+ sActivePointersIds->Remove(pointerEvent->pointerId);
+ }
+ }
+ break;
+ case eMouseExitFromWidget:
+ // In this case we have to remove information about disappeared mouse
+ // pointers
+ if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
+ sActivePointersIds->Remove(mouseEvent->pointerId);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+nsIContent*
+PresShell::GetCurrentEventContent()
+{
+ if (mCurrentEventContent &&
+ mCurrentEventContent->GetComposedDoc() != mDocument) {
+ mCurrentEventContent = nullptr;
+ mCurrentEventFrame = nullptr;
+ }
+ return mCurrentEventContent;
+}
+
+nsIFrame*
+PresShell::GetCurrentEventFrame()
+{
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return nullptr;
+ }
+
+ // GetCurrentEventContent() makes sure the content is still in the
+ // same document that this pres shell belongs to. If not, then the
+ // frame shouldn't get an event, nor should we even assume its safe
+ // to try and find the frame.
+ nsIContent* content = GetCurrentEventContent();
+ if (!mCurrentEventFrame && content) {
+ mCurrentEventFrame = content->GetPrimaryFrame();
+ MOZ_ASSERT(!mCurrentEventFrame ||
+ mCurrentEventFrame->PresContext()->GetPresShell() == this);
+ }
+ return mCurrentEventFrame;
+}
+
+nsIFrame*
+PresShell::GetEventTargetFrame()
+{
+ return GetCurrentEventFrame();
+}
+
+already_AddRefed<nsIContent>
+PresShell::GetEventTargetContent(WidgetEvent* aEvent)
+{
+ nsCOMPtr<nsIContent> content = GetCurrentEventContent();
+ if (!content) {
+ nsIFrame* currentEventFrame = GetCurrentEventFrame();
+ if (currentEventFrame) {
+ currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
+ NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
+ "handing out content from a different doc");
+ }
+ }
+ return content.forget();
+}
+
+void
+PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent)
+{
+ if (mCurrentEventFrame || mCurrentEventContent) {
+ mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
+ mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
+ }
+ mCurrentEventFrame = aFrame;
+ mCurrentEventContent = aContent;
+}
+
+void
+PresShell::PopCurrentEventInfo()
+{
+ mCurrentEventFrame = nullptr;
+ mCurrentEventContent = nullptr;
+
+ if (0 != mCurrentEventFrameStack.Length()) {
+ mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
+ mCurrentEventFrameStack.RemoveElementAt(0);
+ mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
+ mCurrentEventContentStack.RemoveObjectAt(0);
+
+ // Don't use it if it has moved to a different document.
+ if (mCurrentEventContent &&
+ mCurrentEventContent->GetComposedDoc() != mDocument) {
+ mCurrentEventContent = nullptr;
+ mCurrentEventFrame = nullptr;
+ }
+ }
+}
+
+bool PresShell::InZombieDocument(nsIContent *aContent)
+{
+ // If a content node points to a null document, or the document is not
+ // attached to a window, then it is possibly in a zombie document,
+ // about to be replaced by a newly loading document.
+ // Such documents cannot handle DOM events.
+ // It might actually be in a node not attached to any document,
+ // in which case there is not parent presshell to retarget it to.
+ nsIDocument* doc = aContent->GetComposedDoc();
+ return !doc || !doc->GetWindow();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+PresShell::GetRootWindow()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (window) {
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
+ NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL");
+ return rootWindow.forget();
+ }
+
+ // If we don't have DOM window, we're zombie, we should find the root window
+ // with our parent shell.
+ nsCOMPtr<nsIPresShell> parent = GetParentPresShellForEventHandling();
+ NS_ENSURE_TRUE(parent, nullptr);
+ return parent->GetRootWindow();
+}
+
+already_AddRefed<nsIPresShell>
+PresShell::GetParentPresShellForEventHandling()
+{
+ NS_ENSURE_TRUE(mPresContext, nullptr);
+
+ // Now, find the parent pres shell and send the event there
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = mPresContext->GetDocShell();
+ if (!treeItem) {
+ treeItem = mForwardingContainer.get();
+ }
+
+ // Might have gone away, or never been around to start with
+ NS_ENSURE_TRUE(treeItem, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+ nsCOMPtr<nsIDocShell> parentDocShell = do_QueryInterface(parentTreeItem);
+ NS_ENSURE_TRUE(parentDocShell && treeItem != parentTreeItem, nullptr);
+
+ nsCOMPtr<nsIPresShell> parentPresShell = parentDocShell->GetPresShell();
+ return parentPresShell.forget();
+}
+
+nsresult
+PresShell::RetargetEventToParent(WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ // Send this events straight up to the parent pres shell.
+ // We do this for keystroke events in zombie documents or if either a frame
+ // or a root content is not present.
+ // That way at least the UI key bindings can work.
+
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
+ nsCOMPtr<nsIPresShell> parentPresShell = GetParentPresShellForEventHandling();
+ NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE);
+
+ // Fake the event as though it's from the parent pres shell's root frame.
+ return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(), aEvent, true, aEventStatus);
+}
+
+void
+PresShell::DisableNonTestMouseEvents(bool aDisable)
+{
+ sDisableNonTestMouseEvents = aDisable;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+PresShell::GetFocusedDOMWindowInOurWindow()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
+ NS_ENSURE_TRUE(rootWindow, nullptr);
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(rootWindow, true,
+ getter_AddRefs(focusedWindow));
+ return focusedWindow.forget();
+}
+
+void
+PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent)
+{
+ if (!mPresContext)
+ return;
+
+ if (!mPresContext->IsRoot()) {
+ PresShell* rootPresShell = GetRootPresShell();
+ if (rootPresShell) {
+ rootPresShell->RecordMouseLocation(aEvent);
+ }
+ return;
+ }
+
+ if ((aEvent->mMessage == eMouseMove &&
+ aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
+ aEvent->mMessage == eMouseEnterIntoWidget ||
+ aEvent->mMessage == eMouseDown ||
+ aEvent->mMessage == eMouseUp) {
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ nsView* rootView = mViewManager->GetRootView();
+ mMouseLocation = nsLayoutUtils::TranslateWidgetToView(mPresContext,
+ aEvent->mWidget, aEvent->mRefPoint, rootView);
+ mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
+ } else {
+ mMouseLocation =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, rootFrame);
+ mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
+ }
+#ifdef DEBUG_MOUSE_LOCATION
+ if (aEvent->mMessage == eMouseEnterIntoWidget) {
+ printf("[ps=%p]got mouse enter for %p\n",
+ this, aEvent->mWidget);
+ }
+ printf("[ps=%p]setting mouse location to (%d,%d)\n",
+ this, mMouseLocation.x, mMouseLocation.y);
+#endif
+ if (aEvent->mMessage == eMouseEnterIntoWidget) {
+ SynthesizeMouseMove(false);
+ }
+ } else if (aEvent->mMessage == eMouseExitFromWidget) {
+ // Although we only care about the mouse moving into an area for which this
+ // pres shell doesn't receive mouse move events, we don't check which widget
+ // the mouse exit was for since this seems to vary by platform. Hopefully
+ // this won't matter at all since we'll get the mouse move or enter after
+ // the mouse exit when the mouse moves from one of our widgets into another.
+ mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
+#ifdef DEBUG_MOUSE_LOCATION
+ printf("[ps=%p]got mouse exit for %p\n",
+ this, aEvent->mWidget);
+ printf("[ps=%p]clearing mouse location\n",
+ this);
+#endif
+ }
+}
+
+nsIFrame* GetNearestFrameContainingPresShell(nsIPresShell* aPresShell)
+{
+ nsView* view = aPresShell->GetViewManager()->GetRootView();
+ while (view && !view->GetFrame()) {
+ view = view->GetParent();
+ }
+
+ nsIFrame* frame = nullptr;
+ if (view) {
+ frame = view->GetFrame();
+ }
+
+ return frame;
+}
+
+static bool
+FlushThrottledStyles(nsIDocument *aDocument, void *aData)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell && shell->IsVisible()) {
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ if (presContext->RestyleManager()->IsGecko()) {
+ // XXX stylo: ServoRestyleManager doesn't support animations yet.
+ presContext->RestyleManager()->AsGecko()->UpdateOnlyAnimationStyles();
+ }
+ }
+ }
+
+ aDocument->EnumerateSubDocuments(FlushThrottledStyles, nullptr);
+ return true;
+}
+
+static nsresult
+DispatchPointerFromMouseOrTouch(PresShell* aShell,
+ nsIFrame* aFrame,
+ WidgetGUIEvent* aEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aStatus,
+ nsIContent** aTargetContent)
+{
+ EventMessage pointerMessage = eVoidEvent;
+ if (aEvent->mClass == eMouseEventClass) {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ // 1. If it is not mouse then it is likely will come as touch event
+ // 2. We don't synthesize pointer events for those events that are not
+ // dispatched to DOM.
+ if (!mouseEvent->convertToPointer ||
+ !aEvent->IsAllowedToDispatchDOMEvent()) {
+ return NS_OK;
+ }
+ int16_t button = mouseEvent->button;
+ switch (mouseEvent->mMessage) {
+ case eMouseMove:
+ button = -1;
+ pointerMessage = ePointerMove;
+ break;
+ case eMouseUp:
+ pointerMessage = mouseEvent->buttons ? ePointerMove : ePointerUp;
+ break;
+ case eMouseDown:
+ pointerMessage =
+ mouseEvent->buttons & ~nsContentUtils::GetButtonsFlagForButton(button) ?
+ ePointerMove : ePointerDown;
+ break;
+ default:
+ return NS_OK;
+ }
+
+ WidgetPointerEvent event(*mouseEvent);
+ event.pointerId = mouseEvent->pointerId;
+ event.inputSource = mouseEvent->inputSource;
+ event.mMessage = pointerMessage;
+ event.button = button;
+ event.buttons = mouseEvent->buttons;
+ event.pressure = event.buttons ?
+ mouseEvent->pressure ? mouseEvent->pressure : 0.5f :
+ 0.0f;
+ event.convertToPointer = mouseEvent->convertToPointer = false;
+ aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
+ aTargetContent);
+ } else if (aEvent->mClass == eTouchEventClass) {
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ // loop over all touches and dispatch pointer events on each touch
+ // copy the event
+ switch (touchEvent->mMessage) {
+ case eTouchMove:
+ pointerMessage = ePointerMove;
+ break;
+ case eTouchEnd:
+ pointerMessage = ePointerUp;
+ break;
+ case eTouchStart:
+ pointerMessage = ePointerDown;
+ break;
+ case eTouchCancel:
+ pointerMessage = ePointerCancel;
+ break;
+ default:
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
+ mozilla::dom::Touch* touch = touchEvent->mTouches[i];
+ if (!touch || !touch->convertToPointer) {
+ continue;
+ }
+
+ WidgetPointerEvent event(touchEvent->IsTrusted(), pointerMessage,
+ touchEvent->mWidget);
+ event.mIsPrimary = i == 0;
+ event.pointerId = touch->Identifier();
+ event.mRefPoint = touch->mRefPoint;
+ event.mModifiers = touchEvent->mModifiers;
+ event.mWidth = touch->RadiusX();
+ event.mHeight = touch->RadiusY();
+ event.tiltX = touch->tiltX;
+ event.tiltY = touch->tiltY;
+ event.mTime = touchEvent->mTime;
+ event.mTimeStamp = touchEvent->mTimeStamp;
+ event.mFlags = touchEvent->mFlags;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.buttons = WidgetMouseEvent::eLeftButtonFlag;
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ event.convertToPointer = touch->convertToPointer = false;
+ aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
+ aTargetContent);
+ }
+ }
+ return NS_OK;
+}
+
+class ReleasePointerCaptureCaller
+{
+public:
+ ReleasePointerCaptureCaller() :
+ mPointerId(0),
+ mPointerType(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN),
+ mIsPrimary(false),
+ mIsSet(false)
+ {
+ }
+ ~ReleasePointerCaptureCaller()
+ {
+ if (mIsSet) {
+ nsIPresShell::ReleasePointerCapturingContent(mPointerId);
+ nsIPresShell::CheckPointerCaptureState(mPointerId, mPointerType,
+ mIsPrimary);
+ }
+ }
+ void SetTarget(uint32_t aPointerId, uint16_t aPointerType, bool aIsPrimary)
+ {
+ mPointerId = aPointerId;
+ mPointerType = aPointerType;
+ mIsPrimary = aIsPrimary;
+ mIsSet = true;
+ }
+
+private:
+ int32_t mPointerId;
+ uint16_t mPointerType;
+ bool mIsPrimary;
+ bool mIsSet;
+};
+
+static bool
+CheckPermissionForBeforeAfterKeyboardEvent(Element* aElement)
+{
+ // An element which is chrome-privileged should be able to handle before
+ // events and after events.
+ nsIPrincipal* principal = aElement->NodePrincipal();
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ return true;
+ }
+
+ // An element which has "before-after-keyboard-event" permission should be
+ // able to handle before events and after events.
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ uint32_t permission = nsIPermissionManager::DENY_ACTION;
+ if (permMgr) {
+ permMgr->TestPermissionFromPrincipal(principal, "before-after-keyboard-event", &permission);
+ if (permission == nsIPermissionManager::ALLOW_ACTION) {
+ return true;
+ }
+
+ // Check "embed-apps" permission for later use.
+ permission = nsIPermissionManager::DENY_ACTION;
+ permMgr->TestPermissionFromPrincipal(principal, "embed-apps", &permission);
+ }
+
+ // An element can handle before events and after events if the following
+ // conditions are met:
+ // 1) <iframe mozbrowser mozapp>
+ // 2) it has "embed-apps" permission.
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame(do_QueryInterface(aElement));
+ if ((permission == nsIPermissionManager::ALLOW_ACTION) &&
+ browserFrame && browserFrame->GetReallyIsApp()) {
+ return true;
+ }
+
+ return false;
+}
+
+static void
+BuildTargetChainForBeforeAfterKeyboardEvent(nsINode* aTarget,
+ nsTArray<nsCOMPtr<Element> >& aChain,
+ bool aTargetIsIframe)
+{
+ Element* frameElement;
+ // If event target is not an iframe, skip the event target and get its
+ // parent frame.
+ if (aTargetIsIframe) {
+ frameElement = aTarget->AsElement();
+ } else {
+ nsPIDOMWindowOuter* window = aTarget->OwnerDoc()->GetWindow();
+ frameElement = window ? window->GetFrameElementInternal() : nullptr;
+ }
+
+ // Check permission for all ancestors and add them into the target chain.
+ while (frameElement) {
+ if (CheckPermissionForBeforeAfterKeyboardEvent(frameElement)) {
+ aChain.AppendElement(frameElement);
+ }
+ nsPIDOMWindowOuter* window = frameElement->OwnerDoc()->GetWindow();
+ frameElement = window ? window->GetFrameElementInternal() : nullptr;
+ }
+}
+
+void
+PresShell::DispatchBeforeKeyboardEventInternal(const nsTArray<nsCOMPtr<Element> >& aChain,
+ const WidgetKeyboardEvent& aEvent,
+ size_t& aChainIndex,
+ bool& aDefaultPrevented)
+{
+ size_t length = aChain.Length();
+ if (!CanDispatchEvent(&aEvent) || !length) {
+ return;
+ }
+
+ EventMessage message =
+ (aEvent.mMessage == eKeyDown) ? eBeforeKeyDown : eBeforeKeyUp;
+ nsCOMPtr<EventTarget> eventTarget;
+ // Dispatch before events from the outermost element.
+ for (int32_t i = length - 1; i >= 0; i--) {
+ eventTarget = do_QueryInterface(aChain[i]->OwnerDoc()->GetWindow());
+ if (!eventTarget || !CanDispatchEvent(&aEvent)) {
+ return;
+ }
+
+ aChainIndex = i;
+ InternalBeforeAfterKeyboardEvent beforeEvent(aEvent.IsTrusted(),
+ message, aEvent.mWidget);
+ beforeEvent.AssignBeforeAfterKeyEventData(aEvent, false);
+ EventDispatcher::Dispatch(eventTarget, mPresContext, &beforeEvent);
+
+ if (beforeEvent.DefaultPrevented()) {
+ aDefaultPrevented = true;
+ return;
+ }
+ }
+}
+
+void
+PresShell::DispatchAfterKeyboardEventInternal(const nsTArray<nsCOMPtr<Element> >& aChain,
+ const WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled,
+ size_t aStartOffset)
+{
+ size_t length = aChain.Length();
+ if (!CanDispatchEvent(&aEvent) || !length) {
+ return;
+ }
+
+ EventMessage message =
+ (aEvent.mMessage == eKeyDown) ? eAfterKeyDown : eAfterKeyUp;
+ bool embeddedCancelled = aEmbeddedCancelled;
+ nsCOMPtr<EventTarget> eventTarget;
+ // Dispatch after events from the innermost element.
+ for (uint32_t i = aStartOffset; i < length; i++) {
+ eventTarget = do_QueryInterface(aChain[i]->OwnerDoc()->GetWindow());
+ if (!eventTarget || !CanDispatchEvent(&aEvent)) {
+ return;
+ }
+
+ InternalBeforeAfterKeyboardEvent afterEvent(aEvent.IsTrusted(),
+ message, aEvent.mWidget);
+ afterEvent.AssignBeforeAfterKeyEventData(aEvent, false);
+ afterEvent.mEmbeddedCancelled.SetValue(embeddedCancelled);
+ EventDispatcher::Dispatch(eventTarget, mPresContext, &afterEvent);
+ embeddedCancelled = afterEvent.DefaultPrevented();
+ }
+}
+
+void
+PresShell::DispatchAfterKeyboardEvent(nsINode* aTarget,
+ const WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled)
+{
+ MOZ_ASSERT(aTarget);
+ MOZ_ASSERT(BeforeAfterKeyboardEventEnabled());
+
+ if (NS_WARN_IF(aEvent.mMessage != eKeyDown && aEvent.mMessage != eKeyUp)) {
+ return;
+ }
+
+ // Build up a target chain. Each item in the chain will receive an after event.
+ AutoTArray<nsCOMPtr<Element>, 5> chain;
+ bool targetIsIframe = IsTargetIframe(aTarget);
+ BuildTargetChainForBeforeAfterKeyboardEvent(aTarget, chain, targetIsIframe);
+ DispatchAfterKeyboardEventInternal(chain, aEvent, aEmbeddedCancelled);
+}
+
+bool
+PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const
+{
+ bool rv =
+ mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript();
+ if (aEvent) {
+ rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed());
+ }
+ return rv;
+}
+
+void
+PresShell::HandleKeyboardEvent(nsINode* aTarget,
+ WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled,
+ nsEventStatus* aStatus,
+ EventDispatchingCallback* aEventCB)
+{
+ MOZ_ASSERT(aTarget);
+
+ // return true if the event target is in its child process
+ bool targetIsIframe = IsTargetIframe(aTarget);
+
+ // Dispatch event directly if the event is a keypress event, a key event on
+ // plugin, or there is no need to fire beforeKey* and afterKey* events.
+ if (aEvent.mMessage == eKeyPress ||
+ aEvent.IsKeyEventOnPlugin() ||
+ !BeforeAfterKeyboardEventEnabled()) {
+ ForwardKeyToInputMethodAppOrDispatch(targetIsIframe, aTarget, aEvent,
+ aStatus, aEventCB);
+ return;
+ }
+
+ MOZ_ASSERT(aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyUp);
+
+ // Build up a target chain. Each item in the chain will receive a before event.
+ AutoTArray<nsCOMPtr<Element>, 5> chain;
+ BuildTargetChainForBeforeAfterKeyboardEvent(aTarget, chain, targetIsIframe);
+
+ // Dispatch before events. If each item in the chain consumes the before
+ // event and doesn't prevent the default action, we will go further to
+ // dispatch the actual key event and after events in the reverse order.
+ // Otherwise, only items which has handled the before event will receive an
+ // after event.
+ size_t chainIndex;
+ bool defaultPrevented = false;
+ DispatchBeforeKeyboardEventInternal(chain, aEvent, chainIndex,
+ defaultPrevented);
+
+ // Before event is default-prevented. Dispatch after events with
+ // embeddedCancelled = false to partial items.
+ if (defaultPrevented) {
+ *aStatus = nsEventStatus_eConsumeNoDefault;
+ DispatchAfterKeyboardEventInternal(chain, aEvent, false, chainIndex);
+ // No need to forward the event to child process.
+ aEvent.StopCrossProcessForwarding();
+ return;
+ }
+
+ // Event listeners may kill nsPresContext and nsPresShell.
+ if (!CanDispatchEvent()) {
+ return;
+ }
+
+ if (ForwardKeyToInputMethodAppOrDispatch(targetIsIframe, aTarget, aEvent,
+ aStatus, aEventCB)) {
+ return;
+ }
+
+ if (aEvent.DefaultPrevented()) {
+ // When embedder prevents the default action of actual key event, attribute
+ // 'embeddedCancelled' of after event is false, i.e. |!targetIsIframe|.
+ // On the contrary, if the defult action is prevented by embedded iframe,
+ // 'embeddedCancelled' is true which equals to |!targetIsIframe|.
+ DispatchAfterKeyboardEventInternal(chain, aEvent, !targetIsIframe, chainIndex);
+ return;
+ }
+
+ // Event listeners may kill nsPresContext and nsPresShell.
+ if (targetIsIframe || !CanDispatchEvent()) {
+ return;
+ }
+
+ // Dispatch after events to all items in the chain.
+ DispatchAfterKeyboardEventInternal(chain, aEvent, aEvent.DefaultPrevented());
+}
+
+#ifdef MOZ_B2G
+bool
+PresShell::ForwardKeyToInputMethodApp(nsINode* aTarget,
+ WidgetKeyboardEvent& aEvent,
+ nsEventStatus* aStatus)
+{
+ if (!XRE_IsParentProcess() || aEvent.mIsSynthesizedByTIP ||
+ aEvent.IsKeyEventOnPlugin()) {
+ return false;
+ }
+
+ if (!mHardwareKeyHandler) {
+ nsresult rv;
+ mHardwareKeyHandler =
+ do_GetService("@mozilla.org/HardwareKeyHandler;1", &rv);
+ if (!NS_SUCCEEDED(rv) || !mHardwareKeyHandler) {
+ return false;
+ }
+ }
+
+ if (mHardwareKeyHandler->ForwardKeyToInputMethodApp(aTarget,
+ aEvent.AsKeyboardEvent(),
+ aStatus)) {
+ // No need to dispatch the forwarded keyboard event to it's child process
+ aEvent.mFlags.mNoCrossProcessBoundaryForwarding = true;
+ return true;
+ }
+
+ return false;
+}
+#endif // MOZ_B2G
+
+bool
+PresShell::ForwardKeyToInputMethodAppOrDispatch(bool aIsTargetRemote,
+ nsINode* aTarget,
+ WidgetKeyboardEvent& aEvent,
+ nsEventStatus* aStatus,
+ EventDispatchingCallback* aEventCB)
+{
+#ifndef MOZ_B2G
+ // No need to forward to input-method-app if the platform isn't run on B2G.
+ EventDispatcher::Dispatch(aTarget, mPresContext,
+ &aEvent, nullptr, aStatus, aEventCB);
+ return false;
+#else
+ // In-process case: the event target is in the current process
+ if (!aIsTargetRemote) {
+ if(ForwardKeyToInputMethodApp(aTarget, aEvent, aStatus)) {
+ return true;
+ }
+
+ // If the keyboard event isn't forwarded to the input-method-app,
+ // then it should be dispatched to its event target directly.
+ EventDispatcher::Dispatch(aTarget, mPresContext,
+ &aEvent, nullptr, aStatus, aEventCB);
+
+ return false;
+ }
+
+ // OOP case: the event target is in its child process.
+ // Dispatch the keyboard event to the iframe that embeds the remote
+ // event target first.
+ EventDispatcher::Dispatch(aTarget, mPresContext,
+ &aEvent, nullptr, aStatus, aEventCB);
+
+ // If the event is defaultPrevented, then there is no need to forward it
+ // to the input-method-app.
+ if (aEvent.mFlags.mDefaultPrevented) {
+ return false;
+ }
+
+ // Try forwarding to the input-method-app.
+ return ForwardKeyToInputMethodApp(aTarget, aEvent, aStatus);
+#endif // MOZ_B2G
+}
+
+nsresult
+PresShell::HandleEvent(nsIFrame* aFrame,
+ WidgetGUIEvent* aEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus,
+ nsIContent** aTargetContent)
+{
+#ifdef MOZ_TASK_TRACER
+ // Make touch events, mouse events and hardware key events to be the source
+ // events of TaskTracer, and originate the rest correlation tasks from here.
+ SourceEventType type = SourceEventType::Unknown;
+ if (aEvent->AsTouchEvent()) {
+ type = SourceEventType::Touch;
+ } else if (aEvent->AsMouseEvent()) {
+ type = SourceEventType::Mouse;
+ } else if (aEvent->AsKeyboardEvent()) {
+ type = SourceEventType::Key;
+ }
+ AutoSourceEvent taskTracerEvent(type);
+#endif
+
+ NS_ASSERTION(aFrame, "aFrame should be not null");
+
+ if (sPointerEventEnabled) {
+ nsWeakFrame weakFrame(aFrame);
+ nsCOMPtr<nsIContent> targetContent;
+ DispatchPointerFromMouseOrTouch(this, aFrame, aEvent, aDontRetargetEvents,
+ aEventStatus,
+ getter_AddRefs(targetContent));
+ if (!weakFrame.IsAlive()) {
+ if (targetContent) {
+ aFrame = targetContent->GetPrimaryFrame();
+ if (!aFrame) {
+ PushCurrentEventInfo(aFrame, targetContent);
+ nsresult rv = HandleEventInternal(aEvent, aEventStatus, true);
+ PopCurrentEventInfo();
+ return rv;
+ }
+ } else {
+ return NS_OK;
+ }
+ }
+ }
+
+ if (mIsDestroying ||
+ (sDisableNonTestMouseEvents && !aEvent->mFlags.mIsSynthesizedForTests &&
+ aEvent->HasMouseEventMessage())) {
+ return NS_OK;
+ }
+
+ RecordMouseLocation(aEvent);
+
+ if (AccessibleCaretEnabled(mDocument->GetDocShell())) {
+ // We have to target the focus window because regardless of where the
+ // touch goes, we want to access the copy paste manager.
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
+ nsCOMPtr<nsIDocument> retargetEventDoc =
+ window ? window->GetExtantDoc() : nullptr;
+ nsCOMPtr<nsIPresShell> presShell =
+ retargetEventDoc ? retargetEventDoc->GetShell() : nullptr;
+
+ RefPtr<AccessibleCaretEventHub> eventHub =
+ presShell ? presShell->GetAccessibleCaretEventHub() : nullptr;
+ if (eventHub) {
+ *aEventStatus = eventHub->HandleEvent(aEvent);
+ if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
+ // If the event is consumed, cancel APZC panning by setting
+ // mMultipleActionsPrevented.
+ aEvent->mFlags.mMultipleActionsPrevented = true;
+ return NS_OK;
+ }
+ }
+ }
+
+ if (sPointerEventEnabled) {
+ UpdateActivePointerState(aEvent);
+ }
+
+ if (!nsContentUtils::IsSafeToRunScript() &&
+ aEvent->IsAllowedToDispatchDOMEvent()) {
+ if (aEvent->mClass == eCompositionEventClass) {
+ IMEStateManager::OnCompositionEventDiscarded(
+ aEvent->AsCompositionEvent());
+ }
+#ifdef DEBUG
+ if (aEvent->IsIMERelatedEvent()) {
+ nsPrintfCString warning("%d event is discarded", aEvent->mMessage);
+ NS_WARNING(warning.get());
+ }
+#endif
+ nsContentUtils::WarnScriptWasIgnored(GetDocument());
+ return NS_OK;
+ }
+
+ nsIContent* capturingContent = ((aEvent->mClass == ePointerEventClass ||
+ aEvent->mClass == eWheelEventClass ||
+ aEvent->HasMouseEventMessage())
+ ? GetCapturingContent()
+ : nullptr);
+
+ nsCOMPtr<nsIDocument> retargetEventDoc;
+ if (!aDontRetargetEvents) {
+ // key and IME related events should not cross top level window boundary.
+ // Basically, such input events should be fired only on focused widget.
+ // However, some IMEs might need to clean up composition after focused
+ // window is deactivated. And also some tests on MozMill want to test key
+ // handling on deactivated window because MozMill window can be activated
+ // during tests. So, there is no merit the events should be redirected to
+ // active window. So, the events should be handled on the last focused
+ // content in the last focused DOM window in same top level window.
+ // Note, if no DOM window has been focused yet, we can discard the events.
+ if (aEvent->IsTargetedAtFocusedWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
+ // No DOM window in same top level window has not been focused yet,
+ // discard the events.
+ if (!window) {
+ return NS_OK;
+ }
+
+ retargetEventDoc = window->GetExtantDoc();
+ if (!retargetEventDoc)
+ return NS_OK;
+ } else if (capturingContent) {
+ // if the mouse is being captured then retarget the mouse event at the
+ // document that is being captured.
+ retargetEventDoc = capturingContent->GetComposedDoc();
+#ifdef ANDROID
+ } else if ((aEvent->mClass == eTouchEventClass) ||
+ (aEvent->mClass == eMouseEventClass) ||
+ (aEvent->mClass == eWheelEventClass)) {
+ retargetEventDoc = GetTouchEventTargetDocument();
+#endif
+ }
+
+ if (retargetEventDoc) {
+ nsCOMPtr<nsIPresShell> presShell = retargetEventDoc->GetShell();
+ if (!presShell)
+ return NS_OK;
+
+ if (presShell != this) {
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (!frame) {
+ if (aEvent->mMessage == eQueryTextContent ||
+ aEvent->IsContentCommandEvent()) {
+ return NS_OK;
+ }
+
+ frame = GetNearestFrameContainingPresShell(presShell);
+ }
+
+ if (!frame)
+ return NS_OK;
+
+ nsCOMPtr<nsIPresShell> shell = frame->PresContext()->GetPresShell();
+ return shell->HandleEvent(frame, aEvent, true, aEventStatus);
+ }
+ }
+ }
+
+ if (aEvent->mClass == eKeyboardEventClass &&
+ mDocument && mDocument->EventHandlingSuppressed()) {
+ if (aEvent->mMessage == eKeyDown) {
+ mNoDelayedKeyEvents = true;
+ } else if (!mNoDelayedKeyEvents) {
+ DelayedEvent* event = new DelayedKeyEvent(aEvent->AsKeyboardEvent());
+ if (!mDelayedEvents.AppendElement(event)) {
+ delete event;
+ }
+ }
+ aEvent->mFlags.mIsSuppressedOrDelayed = true;
+ return NS_OK;
+ }
+
+ nsIFrame* frame = aFrame;
+
+ if (aEvent->IsUsingCoordinates()) {
+ ReleasePointerCaptureCaller releasePointerCaptureCaller;
+ if (mDocument) {
+ if (aEvent->mClass == eTouchEventClass) {
+ nsIDocument::UnlockPointer();
+ }
+
+ nsWeakFrame weakFrame(frame);
+ { // scope for scriptBlocker.
+ nsAutoScriptBlocker scriptBlocker;
+ FlushThrottledStyles(GetRootPresShell()->GetDocument(), nullptr);
+ }
+
+
+ if (!weakFrame.IsAlive()) {
+ frame = GetNearestFrameContainingPresShell(this);
+ }
+ }
+
+ if (!frame) {
+ NS_WARNING("Nothing to handle this event!");
+ return NS_OK;
+ }
+
+ nsPresContext* framePresContext = frame->PresContext();
+ nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
+ NS_ASSERTION(rootPresContext == mPresContext->GetRootPresContext(),
+ "How did we end up outside the connected prescontext/viewmanager hierarchy?");
+ // If we aren't starting our event dispatch from the root frame of the root prescontext,
+ // then someone must be capturing the mouse. In that case we don't want to search the popup
+ // list.
+ if (framePresContext == rootPresContext &&
+ frame == mFrameConstructor->GetRootFrame()) {
+ nsIFrame* popupFrame =
+ nsLayoutUtils::GetPopupFrameForEventCoordinates(rootPresContext, aEvent);
+ // If a remote browser is currently capturing input break out if we
+ // detect a chrome generated popup.
+ if (popupFrame && capturingContent &&
+ EventStateManager::IsRemoteTarget(capturingContent)) {
+ capturingContent = nullptr;
+ }
+ // If the popupFrame is an ancestor of the 'frame', the frame should
+ // handle the event, otherwise, the popup should handle it.
+ if (popupFrame &&
+ !nsContentUtils::ContentIsCrossDocDescendantOf(
+ framePresContext->GetPresShell()->GetDocument(),
+ popupFrame->GetContent())) {
+ frame = popupFrame;
+ }
+ }
+
+ bool captureRetarget = false;
+ if (capturingContent) {
+ // If a capture is active, determine if the docshell is visible. If not,
+ // clear the capture and target the mouse event normally instead. This
+ // would occur if the mouse button is held down while a tab change occurs.
+ // If the docshell is visible, look for a scrolling container.
+ bool vis;
+ nsCOMPtr<nsIBaseWindow> baseWin =
+ do_QueryInterface(mPresContext->GetContainerWeak());
+ if (baseWin && NS_SUCCEEDED(baseWin->GetVisibility(&vis)) && vis) {
+ captureRetarget = gCaptureInfo.mRetargetToElement;
+ if (!captureRetarget) {
+ // A check was already done above to ensure that capturingContent is
+ // in this presshell.
+ NS_ASSERTION(capturingContent->GetComposedDoc() == GetDocument(),
+ "Unexpected document");
+ nsIFrame* captureFrame = capturingContent->GetPrimaryFrame();
+ if (captureFrame) {
+ if (capturingContent->IsHTMLElement(nsGkAtoms::select)) {
+ // a dropdown <select> has a child in its selectPopupList and we should
+ // capture on that instead.
+ nsIFrame* childFrame = captureFrame->GetChildList(nsIFrame::kSelectPopupList).FirstChild();
+ if (childFrame) {
+ captureFrame = childFrame;
+ }
+ }
+
+ // scrollable frames should use the scrolling container as
+ // the root instead of the document
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
+ if (scrollFrame) {
+ frame = scrollFrame->GetScrolledFrame();
+ }
+ }
+ }
+ }
+ else {
+ ClearMouseCapture(nullptr);
+ capturingContent = nullptr;
+ }
+ }
+
+ // all touch events except for touchstart use a captured target
+ if (aEvent->mClass == eTouchEventClass && aEvent->mMessage != eTouchStart) {
+ captureRetarget = true;
+ }
+
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ bool isWindowLevelMouseExit = (aEvent->mMessage == eMouseExitFromWidget) &&
+ (mouseEvent && mouseEvent->mExitFrom == WidgetMouseEvent::eTopLevel);
+
+ // Get the frame at the event point. However, don't do this if we're
+ // capturing and retargeting the event because the captured frame will
+ // be used instead below. Also keep using the root frame if we're dealing
+ // with a window-level mouse exit event since we want to start sending
+ // mouse out events at the root EventStateManager.
+ if (!captureRetarget && !isWindowLevelMouseExit) {
+ nsPoint eventPoint;
+ uint32_t flags = 0;
+ if (aEvent->mMessage == eTouchStart) {
+ flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ // if this is a continuing session, ensure that all these events are
+ // in the same document by taking the target of the events already in
+ // the capture list
+ nsCOMPtr<nsIContent> anyTarget;
+ if (touchEvent->mTouches.Length() > 1) {
+ anyTarget = TouchManager::GetAnyCapturedTouchTarget();
+ }
+
+ for (int32_t i = touchEvent->mTouches.Length(); i; ) {
+ --i;
+ dom::Touch* touch = touchEvent->mTouches[i];
+
+ int32_t id = touch->Identifier();
+ if (!TouchManager::HasCapturedTouch(id)) {
+ // find the target for this touch
+ eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
+ touch->mRefPoint,
+ frame);
+ nsIFrame* target = FindFrameTargetedByInputEvent(aEvent,
+ frame,
+ eventPoint,
+ flags);
+ if (target && !anyTarget) {
+ target->GetContentForEvent(aEvent, getter_AddRefs(anyTarget));
+ while (anyTarget && !anyTarget->IsElement()) {
+ anyTarget = anyTarget->GetParent();
+ }
+ touch->SetTarget(anyTarget);
+ } else {
+ nsIFrame* newTargetFrame = nullptr;
+ for (nsIFrame* f = target; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
+ newTargetFrame = f;
+ break;
+ }
+ // We must be in a subdocument so jump directly to the root frame.
+ // GetParentOrPlaceholderForCrossDoc gets called immediately to
+ // jump up to the containing document.
+ f = f->PresContext()->GetPresShell()->GetRootFrame();
+ }
+
+ // if we couldn't find a target frame in the same document as
+ // anyTarget, remove the touch from the capture touch list, as
+ // well as the event->mTouches array. touchmove events that aren't
+ // in the captured touch list will be discarded
+ if (!newTargetFrame) {
+ touchEvent->mTouches.RemoveElementAt(i);
+ } else {
+ target = newTargetFrame;
+ nsCOMPtr<nsIContent> targetContent;
+ target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ while (targetContent && !targetContent->IsElement()) {
+ targetContent = targetContent->GetParent();
+ }
+ touch->SetTarget(targetContent);
+ }
+ }
+ if (target) {
+ frame = target;
+ }
+ } else {
+ // This touch is an old touch, we need to ensure that is not
+ // marked as changed and set its target correctly
+ touch->mChanged = false;
+ int32_t id = touch->Identifier();
+
+ RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
+ if (oldTouch) {
+ touch->SetTarget(oldTouch->mTarget);
+ }
+ }
+ }
+ } else {
+ eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, frame);
+ }
+ if (mouseEvent && mouseEvent->mClass == eMouseEventClass &&
+ mouseEvent->mIgnoreRootScrollFrame) {
+ flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
+ }
+ nsIFrame* target =
+ FindFrameTargetedByInputEvent(aEvent, frame, eventPoint, flags);
+ if (target) {
+ frame = target;
+ }
+ }
+
+ // if a node is capturing the mouse, check if the event needs to be
+ // retargeted at the capturing content instead. This will be the case when
+ // capture retargeting is being used, no frame was found or the frame's
+ // content is not a descendant of the capturing content.
+ if (capturingContent &&
+ (gCaptureInfo.mRetargetToElement || !frame->GetContent() ||
+ !nsContentUtils::ContentIsCrossDocDescendantOf(frame->GetContent(),
+ capturingContent))) {
+ // A check was already done above to ensure that capturingContent is
+ // in this presshell.
+ NS_ASSERTION(capturingContent->GetComposedDoc() == GetDocument(),
+ "Unexpected document");
+ nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
+ if (capturingFrame) {
+ frame = capturingFrame;
+ }
+ }
+
+ if (aEvent->mClass == ePointerEventClass) {
+ if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
+ // Try to keep frame for following check, because
+ // frame can be damaged during CheckPointerCaptureState.
+ nsWeakFrame frameKeeper(frame);
+ // Handle pending pointer capture before any pointer events except
+ // gotpointercapture / lostpointercapture.
+ CheckPointerCaptureState(pointerEvent->pointerId,
+ pointerEvent->inputSource,
+ pointerEvent->mIsPrimary);
+ // Prevent application crashes, in case damaged frame.
+ if (!frameKeeper.IsAlive()) {
+ frame = nullptr;
+ }
+ // Implicit pointer capture for touch
+ if (frame && sPointerEventImplicitCapture &&
+ pointerEvent->mMessage == ePointerDown &&
+ pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+ nsCOMPtr<nsIContent> targetContent;
+ frame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ while (targetContent && !targetContent->IsElement()) {
+ targetContent = targetContent->GetParent();
+ }
+ if (targetContent) {
+ SetPointerCapturingContent(pointerEvent->pointerId, targetContent);
+ }
+ }
+ }
+ }
+
+ if (aEvent->mClass == ePointerEventClass &&
+ aEvent->mMessage != ePointerDown) {
+ if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
+ uint32_t pointerId = pointerEvent->pointerId;
+ nsIContent* pointerCapturingContent =
+ GetPointerCapturingContent(pointerId);
+
+ if (pointerCapturingContent) {
+ if (nsIFrame* capturingFrame = pointerCapturingContent->GetPrimaryFrame()) {
+ // If pointer capture is set, we should suppress
+ // pointerover/pointerenter events for all elements except element
+ // which have pointer capture. (Code in EventStateManager)
+ pointerEvent->retargetedByPointerCapture =
+ frame && frame->GetContent() &&
+ !nsContentUtils::ContentIsDescendantOf(frame->GetContent(),
+ pointerCapturingContent);
+ frame = capturingFrame;
+ }
+
+ if (pointerEvent->mMessage == ePointerUp ||
+ pointerEvent->mMessage == ePointerCancel) {
+ // Implicitly releasing capture for given pointer.
+ // ePointerLostCapture should be send after ePointerUp or
+ // ePointerCancel.
+ releasePointerCaptureCaller.SetTarget(pointerId,
+ pointerEvent->inputSource,
+ pointerEvent->mIsPrimary);
+ }
+ }
+ }
+ }
+
+ // Suppress mouse event if it's being targeted at an element inside
+ // a document which needs events suppressed
+ if (aEvent->mClass == eMouseEventClass &&
+ frame->PresContext()->Document()->EventHandlingSuppressed()) {
+ if (aEvent->mMessage == eMouseDown) {
+ mNoDelayedMouseEvents = true;
+ } else if (!mNoDelayedMouseEvents && (aEvent->mMessage == eMouseUp ||
+ // contextmenu is triggered after right mouseup on Windows and right
+ // mousedown on other platforms.
+ aEvent->mMessage == eContextMenu)) {
+ DelayedEvent* event = new DelayedMouseEvent(aEvent->AsMouseEvent());
+ if (!mDelayedEvents.AppendElement(event)) {
+ delete event;
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!frame) {
+ NS_WARNING("Nothing to handle this event!");
+ return NS_OK;
+ }
+
+ PresShell* shell =
+ static_cast<PresShell*>(frame->PresContext()->PresShell());
+ switch (aEvent->mMessage) {
+ case eTouchMove:
+ case eTouchCancel:
+ case eTouchEnd: {
+ // get the correct shell to dispatch to
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ for (dom::Touch* touch : touchEvent->mTouches) {
+ if (!touch) {
+ break;
+ }
+
+ RefPtr<dom::Touch> oldTouch =
+ TouchManager::GetCapturedTouch(touch->Identifier());
+ if (!oldTouch) {
+ break;
+ }
+
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(oldTouch->GetTarget());
+ if (!content) {
+ break;
+ }
+
+ nsIFrame* contentFrame = content->GetPrimaryFrame();
+ if (!contentFrame) {
+ break;
+ }
+
+ shell = static_cast<PresShell*>(
+ contentFrame->PresContext()->PresShell());
+ if (shell) {
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ // Check if we have an active EventStateManager which isn't the
+ // EventStateManager of the current PresContext.
+ // If that is the case, and mouse is over some ancestor document,
+ // forward event handling to the active document.
+ // This way content can get mouse events even when
+ // mouse is over the chrome or outside the window.
+ //
+ // Note, currently for backwards compatibility we don't forward mouse events
+ // to the active document when mouse is over some subdocument.
+ if (EventStateManager* activeESM = EventStateManager::GetActiveEventStateManager()) {
+ if (aEvent->mClass == ePointerEventClass || aEvent->HasMouseEventMessage()) {
+ if (activeESM != shell->GetPresContext()->EventStateManager()) {
+ if (nsPresContext* activeContext = activeESM->GetPresContext()) {
+ if (nsIPresShell* activeShell = activeContext->GetPresShell()) {
+ if (nsContentUtils::ContentIsCrossDocDescendantOf(activeShell->GetDocument(),
+ shell->GetDocument())) {
+ shell = static_cast<PresShell*>(activeShell);
+ frame = shell->GetRootFrame();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Before HandlePositionedEvent we should save mPointerEventTarget in some
+ // cases
+ nsWeakFrame weakFrame;
+ if (sPointerEventEnabled && aTargetContent &&
+ ePointerEventClass == aEvent->mClass) {
+ weakFrame = frame;
+ shell->mPointerEventTarget = frame->GetContent();
+ MOZ_ASSERT(!frame->GetContent() ||
+ shell->GetDocument() == frame->GetContent()->OwnerDoc());
+ }
+
+ // Prevent deletion until we're done with event handling (bug 336582) and
+ // swap mPointerEventTarget to *aTargetContent
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip(shell);
+ nsresult rv;
+ if (shell != this) {
+ // Handle the event in the correct shell.
+ // We pass the subshell's root frame as the frame to start from. This is
+ // the only correct alternative; if the event was captured then it
+ // must have been captured by us or some ancestor shell and we
+ // now ask the subshell to dispatch it normally.
+ rv = shell->HandlePositionedEvent(frame, aEvent, aEventStatus);
+ } else {
+ rv = HandlePositionedEvent(frame, aEvent, aEventStatus);
+ }
+
+ // After HandlePositionedEvent we should reestablish
+ // content (which still live in tree) in some cases
+ if (sPointerEventEnabled && aTargetContent &&
+ ePointerEventClass == aEvent->mClass) {
+ if (!weakFrame.IsAlive()) {
+ shell->mPointerEventTarget.swap(*aTargetContent);
+ }
+ }
+
+ return rv;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (frame) {
+ PushCurrentEventInfo(nullptr, nullptr);
+
+ // key and IME related events go to the focused frame in this DOM window.
+ if (aEvent->IsTargetedAtFocusedContent()) {
+ mCurrentEventContent = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsCOMPtr<nsIContent> eventTarget =
+ nsFocusManager::GetFocusedDescendant(window, false,
+ getter_AddRefs(focusedWindow));
+
+ // otherwise, if there is no focused content or the focused content has
+ // no frame, just use the root content. This ensures that key events
+ // still get sent to the window properly if nothing is focused or if a
+ // frame goes away while it is focused.
+ if (!eventTarget || !eventTarget->GetPrimaryFrame()) {
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
+ if (htmlDoc) {
+ nsCOMPtr<nsIDOMHTMLElement> body;
+ htmlDoc->GetBody(getter_AddRefs(body));
+ eventTarget = do_QueryInterface(body);
+ if (!eventTarget) {
+ eventTarget = mDocument->GetRootElement();
+ }
+ } else {
+ eventTarget = mDocument->GetRootElement();
+ }
+ }
+
+ if (aEvent->mMessage == eKeyDown) {
+ NS_IF_RELEASE(gKeyDownTarget);
+ NS_IF_ADDREF(gKeyDownTarget = eventTarget);
+ }
+ else if ((aEvent->mMessage == eKeyPress ||
+ aEvent->mMessage == eKeyUp) &&
+ gKeyDownTarget) {
+ // If a different element is now focused for the keypress/keyup event
+ // than what was focused during the keydown event, check if the new
+ // focused element is not in a chrome document any more, and if so,
+ // retarget the event back at the keydown target. This prevents a
+ // content area from grabbing the focus from chrome in-between key
+ // events.
+ if (eventTarget) {
+ bool keyDownIsChrome = nsContentUtils::IsChromeDoc(gKeyDownTarget->GetComposedDoc());
+ if (keyDownIsChrome != nsContentUtils::IsChromeDoc(eventTarget->GetComposedDoc()) ||
+ (keyDownIsChrome && TabParent::GetFrom(eventTarget))) {
+ eventTarget = gKeyDownTarget;
+ }
+ }
+
+ if (aEvent->mMessage == eKeyUp) {
+ NS_RELEASE(gKeyDownTarget);
+ }
+ }
+
+ mCurrentEventFrame = nullptr;
+ nsIDocument* targetDoc = eventTarget ? eventTarget->OwnerDoc() : nullptr;
+ if (targetDoc && targetDoc != mDocument) {
+ PopCurrentEventInfo();
+ nsCOMPtr<nsIPresShell> shell = targetDoc->GetShell();
+ if (shell) {
+ rv = static_cast<PresShell*>(shell.get())->
+ HandleRetargetedEvent(aEvent, aEventStatus, eventTarget);
+ }
+ return rv;
+ } else {
+ mCurrentEventContent = eventTarget;
+ }
+
+ if (!GetCurrentEventContent() || !GetCurrentEventFrame() ||
+ InZombieDocument(mCurrentEventContent)) {
+ rv = RetargetEventToParent(aEvent, aEventStatus);
+ PopCurrentEventInfo();
+ return rv;
+ }
+ } else {
+ mCurrentEventFrame = frame;
+ }
+ if (GetCurrentEventFrame()) {
+ rv = HandleEventInternal(aEvent, aEventStatus, true);
+ }
+
+#ifdef DEBUG
+ ShowEventTargetDebug();
+#endif
+ PopCurrentEventInfo();
+ } else {
+ // Activation events need to be dispatched even if no frame was found, since
+ // we don't want the focus to be out of sync.
+
+ if (!NS_EVENT_NEEDS_FRAME(aEvent)) {
+ mCurrentEventFrame = nullptr;
+ return HandleEventInternal(aEvent, aEventStatus, true);
+ }
+ else if (aEvent->HasKeyEventMessage()) {
+ // Keypress events in new blank tabs should not be completely thrown away.
+ // Retarget them -- the parent chrome shell might make use of them.
+ return RetargetEventToParent(aEvent, aEventStatus);
+ }
+ }
+
+ return rv;
+}
+
+#ifdef ANDROID
+nsIDocument*
+PresShell::GetTouchEventTargetDocument()
+{
+ nsPresContext* context = GetPresContext();
+ if (!context || !context->IsRoot()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
+ if (!shellAsTreeItem) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner));
+ if (!owner) {
+ return nullptr;
+ }
+
+ // now get the primary content shell (active tab)
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ owner->GetPrimaryContentShell(getter_AddRefs(item));
+ nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item);
+ if (!childDocShell) {
+ return nullptr;
+ }
+
+ return childDocShell->GetDocument();
+}
+#endif
+
+#ifdef DEBUG
+void
+PresShell::ShowEventTargetDebug()
+{
+ if (nsFrame::GetShowEventTargetFrameBorder() &&
+ GetCurrentEventFrame()) {
+ if (mDrawEventTargetFrame) {
+ mDrawEventTargetFrame->InvalidateFrame();
+ }
+
+ mDrawEventTargetFrame = mCurrentEventFrame;
+ mDrawEventTargetFrame->InvalidateFrame();
+ }
+}
+#endif
+
+nsresult
+PresShell::HandlePositionedEvent(nsIFrame* aTargetFrame,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ nsresult rv = NS_OK;
+
+ PushCurrentEventInfo(nullptr, nullptr);
+
+ mCurrentEventFrame = aTargetFrame;
+
+ if (mCurrentEventFrame) {
+ nsCOMPtr<nsIContent> targetElement;
+ mCurrentEventFrame->GetContentForEvent(aEvent,
+ getter_AddRefs(targetElement));
+
+ // If there is no content for this frame, target it anyway. Some
+ // frames can be targeted but do not have content, particularly
+ // windows with scrolling off.
+ if (targetElement) {
+ // Bug 103055, bug 185889: mouse events apply to *elements*, not all
+ // nodes. Thus we get the nearest element parent here.
+ // XXX we leave the frame the same even if we find an element
+ // parent, so that the text frame will receive the event (selection
+ // and friends are the ones who care about that anyway)
+ //
+ // We use weak pointers because during this tight loop, the node
+ // will *not* go away. And this happens on every mousemove.
+ while (targetElement && !targetElement->IsElement()) {
+ targetElement = targetElement->GetFlattenedTreeParent();
+ }
+
+ // If we found an element, target it. Otherwise, target *nothing*.
+ if (!targetElement) {
+ mCurrentEventContent = nullptr;
+ mCurrentEventFrame = nullptr;
+ } else if (targetElement != mCurrentEventContent) {
+ mCurrentEventContent = targetElement;
+ }
+ }
+ }
+
+ if (GetCurrentEventFrame()) {
+ rv = HandleEventInternal(aEvent, aEventStatus, true);
+ }
+
+#ifdef DEBUG
+ ShowEventTargetDebug();
+#endif
+ PopCurrentEventInfo();
+ return rv;
+}
+
+nsresult
+PresShell::HandleEventWithTarget(WidgetEvent* aEvent, nsIFrame* aFrame,
+ nsIContent* aContent, nsEventStatus* aStatus)
+{
+#if DEBUG
+ MOZ_ASSERT(!aFrame || aFrame->PresContext()->GetPresShell() == this,
+ "wrong shell");
+ if (aContent) {
+ nsIDocument* doc = aContent->GetComposedDoc();
+ NS_ASSERTION(doc, "event for content that isn't in a document");
+ NS_ASSERTION(!doc || doc->GetShell() == this, "wrong shell");
+ }
+#endif
+ NS_ENSURE_STATE(!aContent || aContent->GetComposedDoc() == mDocument);
+
+ PushCurrentEventInfo(aFrame, aContent);
+ nsresult rv = HandleEventInternal(aEvent, aStatus, false);
+ PopCurrentEventInfo();
+ return rv;
+}
+
+nsresult
+PresShell::HandleEventInternal(WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ bool aIsHandlingNativeEvent)
+{
+ RefPtr<EventStateManager> manager = mPresContext->EventStateManager();
+ nsresult rv = NS_OK;
+
+ if (!NS_EVENT_NEEDS_FRAME(aEvent) || GetCurrentEventFrame() || GetCurrentEventContent()) {
+ bool touchIsNew = false;
+ bool isHandlingUserInput = false;
+
+ // XXX How about IME events and input events for plugins?
+ if (aEvent->IsTrusted()) {
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp: {
+ nsIDocument* doc = GetCurrentEventContent() ?
+ mCurrentEventContent->OwnerDoc() : nullptr;
+ auto keyCode = aEvent->AsKeyboardEvent()->mKeyCode;
+ if (keyCode == NS_VK_ESCAPE) {
+ nsIDocument* root = nsContentUtils::GetRootDocument(doc);
+ if (root && root->GetFullscreenElement()) {
+ // Prevent default action on ESC key press when exiting
+ // DOM fullscreen mode. This prevents the browser ESC key
+ // handler from stopping all loads in the document, which
+ // would cause <video> loads to stop.
+ // XXX We need to claim the Escape key event which will be
+ // dispatched only into chrome is already consumed by
+ // content because we need to prevent its default here
+ // for some reasons (not sure) but we need to detect
+ // if a chrome event handler will call PreventDefault()
+ // again and check it later.
+ aEvent->PreventDefaultBeforeDispatch();
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+
+ // The event listeners in chrome can prevent this ESC behavior by
+ // calling prevent default on the preceding keydown/press events.
+ if (!mIsLastChromeOnlyEscapeKeyConsumed &&
+ aEvent->mMessage == eKeyUp) {
+ // ESC key released while in DOM fullscreen mode.
+ // Fully exit all browser windows and documents from
+ // fullscreen mode.
+ nsIDocument::AsyncExitFullscreen(nullptr);
+ }
+ }
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (!mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
+ // XXX See above comment to understand the reason why this needs
+ // to claim that the Escape key event is consumed by content
+ // even though it will be dispatched only into chrome.
+ aEvent->PreventDefaultBeforeDispatch();
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ if (aEvent->mMessage == eKeyUp) {
+ nsIDocument::UnlockPointer();
+ }
+ }
+ }
+ if (keyCode != NS_VK_ESCAPE && keyCode != NS_VK_SHIFT &&
+ keyCode != NS_VK_CONTROL && keyCode != NS_VK_ALT &&
+ keyCode != NS_VK_WIN && keyCode != NS_VK_META) {
+ // Allow keys other than ESC and modifiers be marked as a
+ // valid user input for triggering popup, fullscreen, and
+ // pointer lock.
+ isHandlingUserInput = true;
+ }
+ break;
+ }
+ case eMouseDown:
+ case eMouseUp:
+ isHandlingUserInput = true;
+ break;
+
+ case eDrop: {
+ nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
+ if (session) {
+ bool onlyChromeDrop = false;
+ session->GetOnlyChromeDrop(&onlyChromeDrop);
+ if (onlyChromeDrop) {
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!mTouchManager.PreHandleEvent(aEvent, aStatus,
+ touchIsNew, isHandlingUserInput,
+ mCurrentEventContent)) {
+ return NS_OK;
+ }
+ }
+
+ if (aEvent->mMessage == eContextMenu) {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsContextMenuKeyEvent() &&
+ !AdjustContextMenuKeyEvent(mouseEvent)) {
+ return NS_OK;
+ }
+ if (mouseEvent->IsShift()) {
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
+ }
+ }
+
+ AutoHandlingUserInputStatePusher userInpStatePusher(isHandlingUserInput,
+ aEvent, mDocument);
+
+ if (aEvent->IsTrusted() && aEvent->mMessage == eMouseMove) {
+ nsIPresShell::AllowMouseCapture(
+ EventStateManager::GetActiveEventStateManager() == manager);
+ }
+
+ nsAutoPopupStatePusher popupStatePusher(
+ Event::GetEventPopupControlState(aEvent));
+
+ // FIXME. If the event was reused, we need to clear the old target,
+ // bug 329430
+ aEvent->mTarget = nullptr;
+
+ // 1. Give event to event manager for pre event state changes and
+ // generation of synthetic events.
+ rv = manager->PreHandleEvent(mPresContext, aEvent, mCurrentEventFrame,
+ mCurrentEventContent, aStatus);
+
+ // 2. Give event to the DOM for third party and JS use.
+ if (NS_SUCCEEDED(rv)) {
+ bool wasHandlingKeyBoardEvent =
+ nsContentUtils::IsHandlingKeyBoardEvent();
+ if (aEvent->mClass == eKeyboardEventClass) {
+ nsContentUtils::SetIsHandlingKeyBoardEvent(true);
+ }
+ if (aEvent->IsAllowedToDispatchDOMEvent()) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
+ "Somebody changed aEvent to cause a DOM event!");
+ nsPresShellEventCB eventCB(this);
+ if (aEvent->mClass == eTouchEventClass) {
+ DispatchTouchEventToDOM(aEvent, aStatus, &eventCB, touchIsNew);
+ } else {
+ DispatchEventToDOM(aEvent, aStatus, &eventCB);
+ }
+ }
+
+ nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
+
+ // 3. Give event to event manager for post event state changes and
+ // generation of synthetic events.
+ if (!mIsDestroying && NS_SUCCEEDED(rv)) {
+ rv = manager->PostHandleEvent(mPresContext, aEvent,
+ GetCurrentEventFrame(), aStatus);
+ }
+ }
+
+ if (!mIsDestroying && aIsHandlingNativeEvent) {
+ // Ensure that notifications to IME should be sent before getting next
+ // native event from the event queue.
+ // XXX Should we check the event message or event class instead of
+ // using aIsHandlingNativeEvent?
+ manager->TryToFlushPendingNotificationsToIME();
+ }
+
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp: {
+ if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
+ if (aEvent->mMessage == eKeyUp) {
+ // Reset this flag after key up is handled.
+ mIsLastChromeOnlyEscapeKeyConsumed = false;
+ } else {
+ if (aEvent->mFlags.mOnlyChromeDispatch &&
+ aEvent->mFlags.mDefaultPreventedByChrome) {
+ mIsLastChromeOnlyEscapeKeyConsumed = true;
+ }
+ }
+ }
+ break;
+ }
+ case eMouseUp:
+ // reset the capturing content now that the mouse button is up
+ SetCapturingContent(nullptr, 0);
+ break;
+ case eMouseMove:
+ nsIPresShell::AllowMouseCapture(false);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (Telemetry::CanRecordBase() &&
+ !aEvent->mTimeStamp.IsNull() &&
+ aEvent->AsInputEvent()) {
+ double millis = (TimeStamp::Now() - aEvent->mTimeStamp).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
+ if (mDocument && mDocument->GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
+ Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis);
+ }
+ }
+
+ return rv;
+}
+
+void
+nsIPresShell::DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture,
+ uint32_t aPointerId,
+ uint16_t aPointerType,
+ bool aIsPrimary,
+ nsIContent* aCaptureTarget)
+{
+ PointerEventInit init;
+ init.mPointerId = aPointerId;
+ init.mBubbles = true;
+ ConvertPointerTypeToString(aPointerType, init.mPointerType);
+ init.mIsPrimary = aIsPrimary;
+ RefPtr<mozilla::dom::PointerEvent> event;
+ event = PointerEvent::Constructor(aCaptureTarget,
+ aIsGotCapture
+ ? NS_LITERAL_STRING("gotpointercapture")
+ : NS_LITERAL_STRING("lostpointercapture"),
+ init);
+ if (event) {
+ bool dummy;
+ // If the capturing element was removed from the DOM tree,
+ // lostpointercapture event should be fired at the document.
+ if (!aIsGotCapture && !aCaptureTarget->IsInUncomposedDoc()) {
+ aCaptureTarget->OwnerDoc()->DispatchEvent(event->InternalDOMEvent(),
+ &dummy);
+ } else {
+ aCaptureTarget->DispatchEvent(event->InternalDOMEvent(), &dummy);
+ }
+ }
+}
+
+nsresult
+PresShell::DispatchEventToDOM(WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ nsPresShellEventCB* aEventCB)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINode> eventTarget = mCurrentEventContent.get();
+ nsPresShellEventCB* eventCBPtr = aEventCB;
+ if (!eventTarget) {
+ nsCOMPtr<nsIContent> targetContent;
+ if (mCurrentEventFrame) {
+ rv = mCurrentEventFrame->
+ GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ }
+ if (NS_SUCCEEDED(rv) && targetContent) {
+ eventTarget = do_QueryInterface(targetContent);
+ } else if (mDocument) {
+ eventTarget = do_QueryInterface(mDocument);
+ // If we don't have any content, the callback wouldn't probably
+ // do nothing.
+ eventCBPtr = nullptr;
+ }
+ }
+ if (eventTarget) {
+ if (aEvent->mClass == eCompositionEventClass) {
+ IMEStateManager::DispatchCompositionEvent(eventTarget, mPresContext,
+ aEvent->AsCompositionEvent(),
+ aStatus, eventCBPtr);
+ } else if (aEvent->mClass == eKeyboardEventClass) {
+ HandleKeyboardEvent(eventTarget, *(aEvent->AsKeyboardEvent()),
+ false, aStatus, eventCBPtr);
+ } else {
+ EventDispatcher::Dispatch(eventTarget, mPresContext,
+ aEvent, nullptr, aStatus, eventCBPtr);
+ }
+ }
+ return rv;
+}
+
+void
+PresShell::DispatchTouchEventToDOM(WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ nsPresShellEventCB* aEventCB,
+ bool aTouchIsNew)
+{
+ // calling preventDefault on touchstart or the first touchmove for a
+ // point prevents mouse events. calling it on the touchend should
+ // prevent click dispatching.
+ bool canPrevent = (aEvent->mMessage == eTouchStart) ||
+ (aEvent->mMessage == eTouchMove && aTouchIsNew) ||
+ (aEvent->mMessage == eTouchEnd);
+ bool preventDefault = false;
+ nsEventStatus tmpStatus = nsEventStatus_eIgnore;
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+
+ // loop over all touches and dispatch events on any that have changed
+ for (dom::Touch* touch : touchEvent->mTouches) {
+ if (!touch || !touch->mChanged) {
+ continue;
+ }
+
+ nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
+ if (!content) {
+ continue;
+ }
+
+ nsIDocument* doc = content->OwnerDoc();
+ nsIContent* capturingContent = GetCapturingContent();
+ if (capturingContent) {
+ if (capturingContent->OwnerDoc() != doc) {
+ // Wrong document, don't dispatch anything.
+ continue;
+ }
+ content = capturingContent;
+ }
+ // copy the event
+ WidgetTouchEvent newEvent(touchEvent->IsTrusted(),
+ touchEvent->mMessage, touchEvent->mWidget);
+ newEvent.AssignTouchEventData(*touchEvent, false);
+ newEvent.mTarget = targetPtr;
+
+ RefPtr<PresShell> contentPresShell;
+ if (doc == mDocument) {
+ contentPresShell = static_cast<PresShell*>(doc->GetShell());
+ if (contentPresShell) {
+ //XXXsmaug huge hack. Pushing possibly capturing content,
+ // even though event target is something else.
+ contentPresShell->PushCurrentEventInfo(
+ content->GetPrimaryFrame(), content);
+ }
+ }
+
+ nsIPresShell *presShell = doc->GetShell();
+ if (!presShell) {
+ continue;
+ }
+
+ nsPresContext *context = presShell->GetPresContext();
+
+ tmpStatus = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(targetPtr, context,
+ &newEvent, nullptr, &tmpStatus, aEventCB);
+ if (nsEventStatus_eConsumeNoDefault == tmpStatus ||
+ newEvent.mFlags.mMultipleActionsPrevented) {
+ preventDefault = true;
+ }
+
+ if (newEvent.mFlags.mMultipleActionsPrevented) {
+ touchEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+
+ if (contentPresShell) {
+ contentPresShell->PopCurrentEventInfo();
+ }
+ }
+
+ if (preventDefault && canPrevent) {
+ *aStatus = nsEventStatus_eConsumeNoDefault;
+ } else {
+ *aStatus = nsEventStatus_eIgnore;
+ }
+}
+
+// Dispatch event to content only (NOT full processing)
+// See also HandleEventWithTarget which does full event processing.
+nsresult
+PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ WidgetEvent* aEvent,
+ nsEventStatus* aStatus)
+{
+ nsresult rv = NS_OK;
+
+ PushCurrentEventInfo(nullptr, aTargetContent);
+
+ // Bug 41013: Check if the event should be dispatched to content.
+ // It's possible that we are in the middle of destroying the window
+ // and the js context is out of date. This check detects the case
+ // that caused a crash in bug 41013, but there may be a better way
+ // to handle this situation!
+ nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
+ if (container) {
+
+ // Dispatch event to content
+ rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
+ nullptr, aStatus);
+ }
+
+ PopCurrentEventInfo();
+ return rv;
+}
+
+// See the method above.
+nsresult
+PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ nsIDOMEvent* aEvent,
+ nsEventStatus* aStatus)
+{
+ nsresult rv = NS_OK;
+
+ PushCurrentEventInfo(nullptr, aTargetContent);
+ nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
+ if (container) {
+ rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
+ mPresContext, aStatus);
+ }
+
+ PopCurrentEventInfo();
+ return rv;
+}
+
+bool
+PresShell::AdjustContextMenuKeyEvent(WidgetMouseEvent* aEvent)
+{
+#ifdef MOZ_XUL
+ // if a menu is open, open the context menu relative to the active item on the menu.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsIFrame* popupFrame = pm->GetTopPopup(ePopupTypeMenu);
+ if (popupFrame) {
+ nsIFrame* itemFrame =
+ (static_cast<nsMenuPopupFrame *>(popupFrame))->GetCurrentMenuItem();
+ if (!itemFrame)
+ itemFrame = popupFrame;
+
+ nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
+ aEvent->mWidget = widget;
+ LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
+ aEvent->mRefPoint = LayoutDeviceIntPoint::FromUnknownPoint(
+ itemFrame->GetScreenRect().BottomLeft()) - widgetPoint;
+
+ mCurrentEventContent = itemFrame->GetContent();
+ mCurrentEventFrame = itemFrame;
+
+ return true;
+ }
+ }
+#endif
+
+ // If we're here because of the key-equiv for showing context menus, we
+ // have to twiddle with the NS event to make sure the context menu comes
+ // up in the upper left of the relevant content area before we create
+ // the DOM event. Since we never call InitMouseEvent() on the event,
+ // the client X/Y will be 0,0. We can make use of that if the widget is null.
+ // Use the root view manager's widget since it's most likely to have one,
+ // and the coordinates returned by GetCurrentItemAndPositionForElement
+ // are relative to the widget of the root of the root view manager.
+ nsRootPresContext* rootPC = mPresContext->GetRootPresContext();
+ aEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
+ if (rootPC) {
+ rootPC->PresShell()->GetViewManager()->
+ GetRootWidget(getter_AddRefs(aEvent->mWidget));
+
+ if (aEvent->mWidget) {
+ // default the refpoint to the topleft of our document
+ nsPoint offset(0, 0);
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (rootFrame) {
+ nsView* view = rootFrame->GetClosestView(&offset);
+ offset += view->GetOffsetToWidget(aEvent->mWidget);
+ aEvent->mRefPoint =
+ LayoutDeviceIntPoint::FromAppUnitsToNearest(offset, mPresContext->AppUnitsPerDevPixel());
+ }
+ }
+ } else {
+ aEvent->mWidget = nullptr;
+ }
+
+ // see if we should use the caret position for the popup
+ LayoutDeviceIntPoint caretPoint;
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ if (PrepareToUseCaretPosition(aEvent->mWidget, caretPoint)) {
+ // caret position is good
+ aEvent->mRefPoint = caretPoint;
+ return true;
+ }
+
+ // If we're here because of the key-equiv for showing context menus, we
+ // have to reset the event target to the currently focused element. Get it
+ // from the focus controller.
+ nsCOMPtr<nsIDOMElement> currentFocus;
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ fm->GetFocusedElement(getter_AddRefs(currentFocus));
+
+ // Reset event coordinates relative to focused frame in view
+ if (currentFocus) {
+ nsCOMPtr<nsIContent> currentPointElement;
+ GetCurrentItemAndPositionForElement(currentFocus,
+ getter_AddRefs(currentPointElement),
+ aEvent->mRefPoint,
+ aEvent->mWidget);
+ if (currentPointElement) {
+ mCurrentEventContent = currentPointElement;
+ mCurrentEventFrame = nullptr;
+ GetCurrentEventFrame();
+ }
+ }
+
+ return true;
+}
+
+// PresShell::PrepareToUseCaretPosition
+//
+// This checks to see if we should use the caret position for popup context
+// menus. Returns true if the caret position should be used, and the
+// coordinates of that position is returned in aTargetPt. This function
+// will also scroll the window as needed to make the caret visible.
+//
+// The event widget should be the widget that generated the event, and
+// whose coordinate system the resulting event's mRefPoint should be
+// relative to. The returned point is in device pixels realtive to the
+// widget passed in.
+bool
+PresShell::PrepareToUseCaretPosition(nsIWidget* aEventWidget,
+ LayoutDeviceIntPoint& aTargetPt)
+{
+ nsresult rv;
+
+ // check caret visibility
+ RefPtr<nsCaret> caret = GetCaret();
+ NS_ENSURE_TRUE(caret, false);
+
+ bool caretVisible = caret->IsVisible();
+ if (!caretVisible)
+ return false;
+
+ // caret selection, this is a temporary weak reference, so no refcounting is
+ // needed
+ nsISelection* domSelection = caret->GetSelection();
+ NS_ENSURE_TRUE(domSelection, false);
+
+ // since the match could be an anonymous textnode inside a
+ // <textarea> or text <input>, we need to get the outer frame
+ // note: frames are not refcounted
+ nsIFrame* frame = nullptr; // may be nullptr
+ nsCOMPtr<nsIDOMNode> node;
+ rv = domSelection->GetFocusNode(getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(node, false);
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ if (content) {
+ nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
+ content = nonNative;
+ }
+
+ if (content) {
+ // It seems like ScrollSelectionIntoView should be enough, but it's
+ // not. The problem is that scrolling the selection into view when it is
+ // below the current viewport will align the top line of the frame exactly
+ // with the bottom of the window. This is fine, BUT, the popup event causes
+ // the control to be re-focused which does this exact call to
+ // ScrollContentIntoView, which has a one-pixel disagreement of whether the
+ // frame is actually in view. The result is that the frame is aligned with
+ // the top of the window, but the menu is still at the bottom.
+ //
+ // Doing this call first forces the frame to be in view, eliminating the
+ // problem. The only difference in the result is that if your cursor is in
+ // an edit box below the current view, you'll get the edit box aligned with
+ // the top of the window. This is arguably better behavior anyway.
+ rv = ScrollContentIntoView(content,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+ NS_ENSURE_SUCCESS(rv, false);
+ frame = content->GetPrimaryFrame();
+ NS_WARNING_ASSERTION(frame, "No frame for focused content?");
+ }
+
+ // Actually scroll the selection (ie caret) into view. Note that this must
+ // be synchronous since we will be checking the caret position on the screen.
+ //
+ // Be easy about errors, and just don't scroll in those cases. Better to have
+ // the correct menu at a weird place than the wrong menu.
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ nsCOMPtr<nsISelectionController> selCon;
+ if (frame)
+ frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon));
+ else
+ selCon = static_cast<nsISelectionController *>(this);
+ if (selCon) {
+ rv = selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ nsPresContext* presContext = GetPresContext();
+
+ // get caret position relative to the closest view
+ nsRect caretCoords;
+ nsIFrame* caretFrame = caret->GetGeometry(&caretCoords);
+ if (!caretFrame)
+ return false;
+ nsPoint viewOffset;
+ nsView* view = caretFrame->GetClosestView(&viewOffset);
+ if (!view)
+ return false;
+ // and then get the caret coords relative to the event widget
+ if (aEventWidget) {
+ viewOffset += view->GetOffsetToWidget(aEventWidget);
+ }
+ caretCoords.MoveBy(viewOffset);
+
+ // caret coordinates are in app units, convert to pixels
+ aTargetPt.x =
+ presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
+ aTargetPt.y =
+ presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
+
+ // make sure rounding doesn't return a pixel which is outside the caret
+ // (e.g. one line lower)
+ aTargetPt.y -= 1;
+
+ return true;
+}
+
+void
+PresShell::GetCurrentItemAndPositionForElement(nsIDOMElement *aCurrentEl,
+ nsIContent** aTargetToUse,
+ LayoutDeviceIntPoint& aTargetPt,
+ nsIWidget *aRootWidget)
+{
+ nsCOMPtr<nsIContent> focusedContent(do_QueryInterface(aCurrentEl));
+ ScrollContentIntoView(focusedContent,
+ ScrollAxis(),
+ ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+
+ nsPresContext* presContext = GetPresContext();
+
+ bool istree = false, checkLineHeight = true;
+ nscoord extraTreeY = 0;
+
+#ifdef MOZ_XUL
+ // Set the position to just underneath the current item for multi-select
+ // lists or just underneath the selected item for single-select lists. If
+ // the element is not a list, or there is no selection, leave the position
+ // as is.
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
+ do_QueryInterface(aCurrentEl);
+ if (multiSelect) {
+ checkLineHeight = false;
+
+ int32_t currentIndex;
+ multiSelect->GetCurrentIndex(&currentIndex);
+ if (currentIndex >= 0) {
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(aCurrentEl));
+ if (xulElement) {
+ nsCOMPtr<nsIBoxObject> box;
+ xulElement->GetBoxObject(getter_AddRefs(box));
+ nsCOMPtr<nsITreeBoxObject> treeBox(do_QueryInterface(box));
+ // Tree view special case (tree items have no frames)
+ // Get the focused row and add its coordinates, which are already in pixels
+ // XXX Boris, should we create a new interface so that this doesn't
+ // need to know about trees? Something like nsINodelessChildCreator which
+ // could provide the current focus coordinates?
+ if (treeBox) {
+ treeBox->EnsureRowIsVisible(currentIndex);
+ int32_t firstVisibleRow, rowHeight;
+ treeBox->GetFirstVisibleRow(&firstVisibleRow);
+ treeBox->GetRowHeight(&rowHeight);
+
+ extraTreeY += presContext->CSSPixelsToAppUnits(
+ (currentIndex - firstVisibleRow + 1) * rowHeight);
+ istree = true;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ treeBox->GetColumns(getter_AddRefs(cols));
+ if (cols) {
+ nsCOMPtr<nsITreeColumn> col;
+ cols->GetFirstColumn(getter_AddRefs(col));
+ if (col) {
+ nsCOMPtr<nsIDOMElement> colElement;
+ col->GetElement(getter_AddRefs(colElement));
+ nsCOMPtr<nsIContent> colContent(do_QueryInterface(colElement));
+ if (colContent) {
+ nsIFrame* frame = colContent->GetPrimaryFrame();
+ if (frame) {
+ extraTreeY += frame->GetSize().height;
+ }
+ }
+ }
+ }
+ }
+ else {
+ multiSelect->GetCurrentItem(getter_AddRefs(item));
+ }
+ }
+ }
+ }
+ else {
+ // don't check menulists as the selected item will be inside a popup.
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aCurrentEl);
+ if (!menulist) {
+ nsCOMPtr<nsIDOMXULSelectControlElement> select =
+ do_QueryInterface(aCurrentEl);
+ if (select) {
+ checkLineHeight = false;
+ select->GetSelectedItem(getter_AddRefs(item));
+ }
+ }
+ }
+
+ if (item)
+ focusedContent = do_QueryInterface(item);
+#endif
+
+ nsIFrame *frame = focusedContent->GetPrimaryFrame();
+ if (frame) {
+ NS_ASSERTION(frame->PresContext() == GetPresContext(),
+ "handling event for focused content that is not in our document?");
+
+ nsPoint frameOrigin(0, 0);
+
+ // Get the frame's origin within its view
+ nsView *view = frame->GetClosestView(&frameOrigin);
+ NS_ASSERTION(view, "No view for frame");
+
+ // View's origin relative the widget
+ if (aRootWidget) {
+ frameOrigin += view->GetOffsetToWidget(aRootWidget);
+ }
+
+ // Start context menu down and to the right from top left of frame
+ // use the lineheight. This is a good distance to move the context
+ // menu away from the top left corner of the frame. If we always
+ // used the frame height, the context menu could end up far away,
+ // for example when we're focused on linked images.
+ // On the other hand, we want to use the frame height if it's less
+ // than the current line height, so that the context menu appears
+ // associated with the correct frame.
+ nscoord extra = 0;
+ if (!istree) {
+ extra = frame->GetSize().height;
+ if (checkLineHeight) {
+ nsIScrollableFrame *scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(frame);
+ if (scrollFrame) {
+ nsSize scrollAmount = scrollFrame->GetLineScrollAmount();
+ nsIFrame* f = do_QueryFrame(scrollFrame);
+ int32_t APD = presContext->AppUnitsPerDevPixel();
+ int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel();
+ scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD);
+ if (extra > scrollAmount.height) {
+ extra = scrollAmount.height;
+ }
+ }
+ }
+ }
+
+ aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x);
+ aTargetPt.y = presContext->AppUnitsToDevPixels(
+ frameOrigin.y + extra + extraTreeY);
+ }
+
+ NS_IF_ADDREF(*aTargetToUse = focusedContent);
+}
+
+bool
+PresShell::ShouldIgnoreInvalidation()
+{
+ return mPaintingSuppressed || !mIsActive || mIsNeverPainting;
+}
+
+void
+PresShell::WillPaint()
+{
+ // Check the simplest things first. In particular, it's important to
+ // check mIsActive before making any of the more expensive calls such
+ // as GetRootPresContext, for the case of a browser with a large
+ // number of tabs.
+ // Don't bother doing anything if some viewmanager in our tree is painting
+ // while we still have painting suppressed or we are not active.
+ if (!mIsActive || mPaintingSuppressed || !IsVisible()) {
+ return;
+ }
+
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (!rootPresContext) {
+ // In some edge cases, such as when we don't have a root frame yet,
+ // we can't find the root prescontext. There's nothing to do in that
+ // case.
+ return;
+ }
+
+ rootPresContext->FlushWillPaintObservers();
+ if (mIsDestroying)
+ return;
+
+ // Process reflows, if we have them, to reduce flicker due to invalidates and
+ // reflow being interspersed. Note that we _do_ allow this to be
+ // interruptible; if we can't do all the reflows it's better to flicker a bit
+ // than to freeze up.
+ FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, false));
+}
+
+void
+PresShell::WillPaintWindow()
+{
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext != mPresContext) {
+ // This could be a popup's presshell. We don't allow plugins in popups
+ // so there's nothing to do here.
+ return;
+ }
+
+#ifndef XP_MACOSX
+ rootPresContext->ApplyPluginGeometryUpdates();
+#endif
+}
+
+void
+PresShell::DidPaintWindow()
+{
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext != mPresContext) {
+ // This could be a popup's presshell. No point in notifying XPConnect
+ // about compositing of popups.
+ return;
+ }
+
+ if (!mHasReceivedPaintMessage) {
+ mHasReceivedPaintMessage = true;
+
+ nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
+ if (obsvc && mDocument) {
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(window));
+ if (chromeWin) {
+ obsvc->NotifyObservers(chromeWin, "widget-first-paint", nullptr);
+ }
+ }
+ }
+}
+
+bool
+PresShell::IsVisible()
+{
+ if (!mIsActive || !mViewManager)
+ return false;
+
+ nsView* view = mViewManager->GetRootView();
+ if (!view)
+ return true;
+
+ // inner view of subdoc frame
+ view = view->GetParent();
+ if (!view)
+ return true;
+
+ // subdoc view
+ view = view->GetParent();
+ if (!view)
+ return true;
+
+ nsIFrame* frame = view->GetFrame();
+ if (!frame)
+ return true;
+
+ return frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY);
+}
+
+nsresult
+PresShell::GetAgentStyleSheets(nsTArray<RefPtr<StyleSheet>>& aSheets)
+{
+ aSheets.Clear();
+ int32_t sheetCount = mStyleSet->SheetCount(SheetType::Agent);
+
+ if (!aSheets.SetCapacity(sheetCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (int32_t i = 0; i < sheetCount; ++i) {
+ StyleSheet* sheet = mStyleSet->StyleSheetAt(SheetType::Agent, i);
+ aSheets.AppendElement(sheet);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+PresShell::SetAgentStyleSheets(const nsTArray<RefPtr<StyleSheet>>& aSheets)
+{
+ return mStyleSet->ReplaceSheets(SheetType::Agent, aSheets);
+}
+
+nsresult
+PresShell::AddOverrideStyleSheet(StyleSheet* aSheet)
+{
+ return mStyleSet->PrependStyleSheet(SheetType::Override, aSheet);
+}
+
+nsresult
+PresShell::RemoveOverrideStyleSheet(StyleSheet* aSheet)
+{
+ return mStyleSet->RemoveStyleSheet(SheetType::Override, aSheet);
+}
+
+static void
+FreezeElement(nsISupports *aSupports, void * /* unused */)
+{
+ nsCOMPtr<nsIObjectLoadingContent> olc(do_QueryInterface(aSupports));
+ if (olc) {
+ olc->StopPluginInstance();
+ }
+}
+
+static bool
+FreezeSubDocument(nsIDocument *aDocument, void *aData)
+{
+ nsIPresShell *shell = aDocument->GetShell();
+ if (shell)
+ shell->Freeze();
+
+ return true;
+}
+
+void
+PresShell::Freeze()
+{
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ MaybeReleaseCapturingContent();
+
+ mDocument->EnumerateActivityObservers(FreezeElement, nullptr);
+
+ if (mCaret) {
+ SetCaretEnabled(false);
+ }
+
+ mPaintingSuppressed = true;
+
+ if (mDocument) {
+ mDocument->EnumerateSubDocuments(FreezeSubDocument, nullptr);
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext &&
+ presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->Freeze();
+ }
+
+ mFrozen = true;
+ if (mDocument) {
+ UpdateImageLockingState();
+ }
+}
+
+void
+PresShell::FireOrClearDelayedEvents(bool aFireEvents)
+{
+ mNoDelayedMouseEvents = false;
+ mNoDelayedKeyEvents = false;
+ if (!aFireEvents) {
+ mDelayedEvents.Clear();
+ return;
+ }
+
+ if (mDocument) {
+ nsCOMPtr<nsIDocument> doc = mDocument;
+ while (!mIsDestroying && mDelayedEvents.Length() &&
+ !doc->EventHandlingSuppressed()) {
+ nsAutoPtr<DelayedEvent> ev(mDelayedEvents[0].forget());
+ mDelayedEvents.RemoveElementAt(0);
+ ev->Dispatch();
+ }
+ if (!doc->EventHandlingSuppressed()) {
+ mDelayedEvents.Clear();
+ }
+ }
+}
+
+static void
+ThawElement(nsISupports *aSupports, void *aShell)
+{
+ nsCOMPtr<nsIObjectLoadingContent> olc(do_QueryInterface(aSupports));
+ if (olc) {
+ olc->AsyncStartPluginInstance();
+ }
+}
+
+static bool
+ThawSubDocument(nsIDocument *aDocument, void *aData)
+{
+ nsIPresShell *shell = aDocument->GetShell();
+ if (shell)
+ shell->Thaw();
+
+ return true;
+}
+
+void
+PresShell::Thaw()
+{
+ nsPresContext* presContext = GetPresContext();
+ if (presContext &&
+ presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->Thaw();
+ }
+
+ mDocument->EnumerateActivityObservers(ThawElement, this);
+
+ if (mDocument)
+ mDocument->EnumerateSubDocuments(ThawSubDocument, nullptr);
+
+ // Get the activeness of our presshell, as this might have changed
+ // while we were in the bfcache
+ QueryIsActive();
+
+ // We're now unfrozen
+ mFrozen = false;
+ UpdateImageLockingState();
+
+ UnsuppressPainting();
+}
+
+//--------------------------------------------------------
+// Start of protected and private methods on the PresShell
+//--------------------------------------------------------
+
+void
+PresShell::MaybeScheduleReflow()
+{
+ ASSERT_REFLOW_SCHEDULED_STATE();
+ if (mReflowScheduled || mIsDestroying || mIsReflowing ||
+ mDirtyRoots.Length() == 0)
+ return;
+
+ if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) {
+ ScheduleReflow();
+ }
+
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+void
+PresShell::ScheduleReflow()
+{
+ NS_PRECONDITION(!mReflowScheduled, "Why are we trying to schedule a reflow?");
+ ASSERT_REFLOW_SCHEDULED_STATE();
+
+ if (GetPresContext()->RefreshDriver()->AddLayoutFlushObserver(this)) {
+ mReflowScheduled = true;
+ }
+
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+nsresult
+PresShell::DidCauseReflow()
+{
+ NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
+ --mChangeNestCount;
+ nsContentUtils::RemoveScriptBlocker();
+
+ return NS_OK;
+}
+
+void
+PresShell::WillDoReflow()
+{
+ mDocument->FlushUserFontSet();
+
+ mPresContext->FlushCounterStyles();
+
+ mFrameConstructor->BeginUpdate();
+
+ mLastReflowStart = GetPerformanceNow();
+}
+
+void
+PresShell::DidDoReflow(bool aInterruptible)
+{
+ mFrameConstructor->EndUpdate();
+
+ HandlePostedReflowCallbacks(aInterruptible);
+
+ nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell();
+ if (docShell) {
+ DOMHighResTimeStamp now = GetPerformanceNow();
+ docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
+ }
+
+ if (sSynthMouseMove) {
+ SynthesizeMouseMove(false);
+ }
+
+ mPresContext->NotifyMissingFonts();
+}
+
+DOMHighResTimeStamp
+PresShell::GetPerformanceNow()
+{
+ DOMHighResTimeStamp now = 0;
+
+ if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
+ Performance* perf = window->GetPerformance();
+
+ if (perf) {
+ now = perf->Now();
+ }
+ }
+
+ return now;
+}
+
+void
+PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell)
+{
+ RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
+
+ NS_PRECONDITION(aTimer == self->mReflowContinueTimer, "Unexpected timer");
+ self->mReflowContinueTimer = nullptr;
+ self->ScheduleReflow();
+}
+
+bool
+PresShell::ScheduleReflowOffTimer()
+{
+ NS_PRECONDITION(!mReflowScheduled, "Shouldn't get here");
+ ASSERT_REFLOW_SCHEDULED_STATE();
+
+ if (!mReflowContinueTimer) {
+ mReflowContinueTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mReflowContinueTimer ||
+ NS_FAILED(mReflowContinueTimer->
+ InitWithFuncCallback(sReflowContinueCallback, this, 30,
+ nsITimer::TYPE_ONE_SHOT))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+PresShell::DoReflow(nsIFrame* target, bool aInterruptible)
+{
+ if (mIsZombie) {
+ return true;
+ }
+
+ gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
+ TimeStamp timeStart;
+ if (tp) {
+ tp->Accumulate();
+ tp->reflowCount++;
+ timeStart = TimeStamp::Now();
+ }
+
+ target->SchedulePaint();
+ nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(target);
+ while (parent) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(parent);
+ parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
+ }
+
+ nsIURI *uri = mDocument->GetDocumentURI();
+ PROFILER_LABEL_PRINTF("PresShell", "DoReflow",
+ js::ProfileEntry::Category::GRAPHICS, "(%s)",
+ uri ? uri->GetSpecOrDefault().get() : "N/A");
+
+ nsDocShell* docShell = static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::START);
+ }
+
+ if (mReflowContinueTimer) {
+ mReflowContinueTimer->Cancel();
+ mReflowContinueTimer = nullptr;
+ }
+
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+
+ // CreateReferenceRenderingContext can return nullptr
+ nsRenderingContext rcx(CreateReferenceRenderingContext());
+
+#ifdef DEBUG
+ mCurrentReflowRoot = target;
+#endif
+
+ // If the target frame is the root of the frame hierarchy, then
+ // use all the available space. If it's simply a `reflow root',
+ // then use the target frame's size as the available space.
+ WritingMode wm = target->GetWritingMode();
+ LogicalSize size(wm);
+ if (target == rootFrame) {
+ size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
+ } else {
+ size = target->GetLogicalSize();
+ }
+
+ NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(),
+ "reflow roots should never split");
+
+ // Don't pass size directly to the reflow state, since a
+ // constrained height implies page/column breaking.
+ LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
+ ReflowInput reflowInput(mPresContext, target, &rcx, reflowSize,
+ ReflowInput::CALLER_WILL_INIT);
+ reflowInput.mOrthogonalLimit = size.BSize(wm);
+
+ if (rootFrame == target) {
+ reflowInput.Init(mPresContext);
+
+ // When the root frame is being reflowed with unconstrained block-size
+ // (which happens when we're called from
+ // nsDocumentViewer::SizeToContent), we're effectively doing a
+ // resize in the block direction, since it changes the meaning of
+ // percentage block-sizes even if no block-sizes actually changed.
+ // The same applies when we reflow again after that computation. This is
+ // an unusual case, and isn't caught by ReflowInput::InitResizeFlags.
+ bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
+
+ if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) {
+ reflowInput.SetBResize(true);
+ }
+
+ mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize;
+ } else {
+ // Initialize reflow state with current used border and padding,
+ // in case this was set specially by the parent frame when the reflow root
+ // was reflowed by its parent.
+ nsMargin currentBorder = target->GetUsedBorder();
+ nsMargin currentPadding = target->GetUsedPadding();
+ reflowInput.Init(mPresContext, nullptr, &currentBorder, &currentPadding);
+ }
+
+ // fix the computed height
+ NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "reflow state should not set margin for reflow roots");
+ if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
+ nscoord computedBSize =
+ size.BSize(wm) - reflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
+ computedBSize = std::max(computedBSize, 0);
+ reflowInput.SetComputedBSize(computedBSize);
+ }
+ NS_ASSERTION(reflowInput.ComputedISize() ==
+ size.ISize(wm) -
+ reflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
+ "reflow state computed incorrect inline size");
+
+ mPresContext->ReflowStarted(aInterruptible);
+ mIsReflowing = true;
+
+ nsReflowStatus status;
+ ReflowOutput desiredSize(reflowInput);
+ target->Reflow(mPresContext, desiredSize, reflowInput, status);
+
+ // If an incremental reflow is initiated at a frame other than the
+ // root frame, then its desired size had better not change! If it's
+ // initiated at the root, then the size better not change unless its
+ // height was unconstrained to start with.
+ nsRect boundsRelativeToTarget = nsRect(0, 0, desiredSize.Width(), desiredSize.Height());
+ NS_ASSERTION((target == rootFrame &&
+ size.BSize(wm) == NS_UNCONSTRAINEDSIZE) ||
+ (desiredSize.ISize(wm) == size.ISize(wm) &&
+ desiredSize.BSize(wm) == size.BSize(wm)),
+ "non-root frame's desired size changed during an "
+ "incremental reflow");
+ NS_ASSERTION(target == rootFrame ||
+ desiredSize.VisualOverflow().IsEqualInterior(boundsRelativeToTarget),
+ "non-root reflow roots must not have visible overflow");
+ NS_ASSERTION(target == rootFrame ||
+ desiredSize.ScrollableOverflow().IsEqualEdges(boundsRelativeToTarget),
+ "non-root reflow roots must not have scrollable overflow");
+ NS_ASSERTION(status == NS_FRAME_COMPLETE,
+ "reflow roots should never split");
+
+ target->SetSize(boundsRelativeToTarget.Size());
+
+ // Always use boundsRelativeToTarget here, not desiredSize.GetVisualOverflowArea(),
+ // because for root frames (where they could be different, since root frames
+ // are allowed to have overflow) the root view bounds need to match the
+ // viewport bounds; the view manager "window dimensions" code depends on it.
+ nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, target,
+ target->GetView(),
+ boundsRelativeToTarget);
+ nsContainerFrame::SyncWindowProperties(mPresContext, target,
+ target->GetView(), &rcx,
+ nsContainerFrame::SET_ASYNC);
+
+ target->DidReflow(mPresContext, nullptr, nsDidReflowStatus::FINISHED);
+ if (target == rootFrame && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
+ mPresContext->SetVisibleArea(boundsRelativeToTarget);
+ }
+
+#ifdef DEBUG
+ mCurrentReflowRoot = nullptr;
+#endif
+
+ NS_ASSERTION(mPresContext->HasPendingInterrupt() ||
+ mFramesToDirty.Count() == 0,
+ "Why do we need to dirty anything if not interrupted?");
+
+ mIsReflowing = false;
+ bool interrupted = mPresContext->HasPendingInterrupt();
+ if (interrupted) {
+ // Make sure target gets reflowed again.
+ for (auto iter = mFramesToDirty.Iter(); !iter.Done(); iter.Next()) {
+ // Mark frames dirty until target frame.
+ nsPtrHashKey<nsIFrame>* p = iter.Get();
+ for (nsIFrame* f = p->GetKey();
+ f && !NS_SUBTREE_DIRTY(f);
+ f = f->GetParent()) {
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ if (f == target) {
+ break;
+ }
+ }
+ }
+
+ NS_ASSERTION(NS_SUBTREE_DIRTY(target), "Why is the target not dirty?");
+ mDirtyRoots.AppendElement(target);
+ mDocument->SetNeedLayoutFlush();
+
+ // Clear mFramesToDirty after we've done the NS_SUBTREE_DIRTY(target)
+ // assertion so that if it fails it's easier to see what's going on.
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+ mFramesToDirty.Clear();
+
+ // Any FlushPendingNotifications with interruptible reflows
+ // should be suppressed now. We don't want to do extra reflow work
+ // before our reflow event happens.
+ mSuppressInterruptibleReflows = true;
+ MaybeScheduleReflow();
+ }
+
+ // dump text perf metrics for reflows with significant text processing
+ if (tp) {
+ if (tp->current.numChars > 100) {
+ TimeDuration reflowTime = TimeStamp::Now() - timeStart;
+ LogTextPerfStats(tp, this, tp->current,
+ reflowTime.ToMilliseconds(), eLog_reflow, nullptr);
+ }
+ tp->Accumulate();
+ }
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::END);
+ }
+
+ return !interrupted;
+}
+
+#ifdef DEBUG
+void
+PresShell::DoVerifyReflow()
+{
+ if (GetVerifyReflowEnable()) {
+ // First synchronously render what we have so far so that we can
+ // see it.
+ nsView* rootView = mViewManager->GetRootView();
+ mViewManager->InvalidateView(rootView);
+
+ FlushPendingNotifications(Flush_Layout);
+ mInVerifyReflow = true;
+ bool ok = VerifyIncrementalReflow();
+ mInVerifyReflow = false;
+ if (VERIFY_REFLOW_ALL & gVerifyReflowFlags) {
+ printf("ProcessReflowCommands: finished (%s)\n",
+ ok ? "ok" : "failed");
+ }
+
+ if (!mDirtyRoots.IsEmpty()) {
+ printf("XXX yikes! reflow commands queued during verify-reflow\n");
+ }
+ }
+}
+#endif
+
+// used with Telemetry metrics
+#define NS_LONG_REFLOW_TIME_MS 5000
+
+bool
+PresShell::ProcessReflowCommands(bool aInterruptible)
+{
+ if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting) {
+ // Nothing to do; bail out
+ return true;
+ }
+
+ mozilla::TimeStamp timerStart = mozilla::TimeStamp::Now();
+ bool interrupted = false;
+ if (!mDirtyRoots.IsEmpty()) {
+
+#ifdef DEBUG
+ if (VERIFY_REFLOW_DUMP_COMMANDS & gVerifyReflowFlags) {
+ printf("ProcessReflowCommands: begin incremental reflow\n");
+ }
+#endif
+
+ // If reflow is interruptible, then make a note of our deadline.
+ const PRIntervalTime deadline = aInterruptible
+ ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
+ : (PRIntervalTime)0;
+
+ // Scope for the reflow entry point
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ WillDoReflow();
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
+ nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
+
+ do {
+ // Send an incremental reflow notification to the target frame.
+ int32_t idx = mDirtyRoots.Length() - 1;
+ nsIFrame *target = mDirtyRoots[idx];
+ mDirtyRoots.RemoveElementAt(idx);
+
+ if (!NS_SUBTREE_DIRTY(target)) {
+ // It's not dirty anymore, which probably means the notification
+ // was posted in the middle of a reflow (perhaps with a reflow
+ // root in the middle). Don't do anything.
+ continue;
+ }
+
+ interrupted = !DoReflow(target, aInterruptible);
+
+ // Keep going until we're out of reflow commands, or we've run
+ // past our deadline, or we're interrupted.
+ } while (!interrupted && !mDirtyRoots.IsEmpty() &&
+ (!aInterruptible || PR_IntervalNow() < deadline));
+
+ interrupted = !mDirtyRoots.IsEmpty();
+ }
+
+ // Exiting the scriptblocker might have killed us
+ if (!mIsDestroying) {
+ DidDoReflow(aInterruptible);
+ }
+
+ // DidDoReflow might have killed us
+ if (!mIsDestroying) {
+#ifdef DEBUG
+ if (VERIFY_REFLOW_DUMP_COMMANDS & gVerifyReflowFlags) {
+ printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
+ (void*)this);
+ }
+ DoVerifyReflow();
+#endif
+
+ // If any new reflow commands were enqueued during the reflow, schedule
+ // another reflow event to process them. Note that we want to do this
+ // after DidDoReflow(), since that method can change whether there are
+ // dirty roots around by flushing, and there's no point in posting a
+ // reflow event just to have the flush revoke it.
+ if (!mDirtyRoots.IsEmpty()) {
+ MaybeScheduleReflow();
+ // And tell our document that we might need flushing
+ mDocument->SetNeedLayoutFlush();
+ }
+ }
+ }
+
+ if (!mIsDestroying && mShouldUnsuppressPainting &&
+ mDirtyRoots.IsEmpty()) {
+ // We only unlock if we're out of reflows. It's pointless
+ // to unlock if reflows are still pending, since reflows
+ // are just going to thrash the frames around some more. By
+ // waiting we avoid an overeager "jitter" effect.
+ mShouldUnsuppressPainting = false;
+ UnsuppressAndInvalidate();
+ }
+
+ if (mDocument->GetRootElement()) {
+ TimeDuration elapsed = TimeStamp::Now() - timerStart;
+ int32_t intElapsed = int32_t(elapsed.ToMilliseconds());
+
+ if (intElapsed > NS_LONG_REFLOW_TIME_MS) {
+ Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE,
+ aInterruptible ? 1 : 0);
+ }
+ }
+
+ return !interrupted;
+}
+
+void
+PresShell::WindowSizeMoveDone()
+{
+ if (mPresContext) {
+ EventStateManager::ClearGlobalActiveContent(nullptr);
+ ClearMouseCapture(nullptr);
+ }
+}
+
+#ifdef MOZ_XUL
+/*
+ * It's better to add stuff to the |DidSetStyleContext| method of the
+ * relevant frames than adding it here. These methods should (ideally,
+ * anyway) go away.
+ */
+
+// Return value says whether to walk children.
+typedef bool (* frameWalkerFn)(nsIFrame *aFrame, void *aClosure);
+
+static bool
+ReResolveMenusAndTrees(nsIFrame *aFrame, void *aClosure)
+{
+ // Trees have a special style cache that needs to be flushed when
+ // the theme changes.
+ nsTreeBodyFrame *treeBody = do_QueryFrame(aFrame);
+ if (treeBody)
+ treeBody->ClearStyleAndImageCaches();
+
+ // We deliberately don't re-resolve style on a menu's popup
+ // sub-content, since doing so slows menus to a crawl. That means we
+ // have to special-case them on a skin switch, and ensure that the
+ // popup frames just get destroyed completely.
+ nsMenuFrame* menu = do_QueryFrame(aFrame);
+ if (menu)
+ menu->CloseMenu(true);
+ return true;
+}
+
+static bool
+ReframeImageBoxes(nsIFrame *aFrame, void *aClosure)
+{
+ nsStyleChangeList *list = static_cast<nsStyleChangeList*>(aClosure);
+ if (aFrame->GetType() == nsGkAtoms::imageBoxFrame) {
+ list->AppendChange(aFrame, aFrame->GetContent(),
+ nsChangeHint_ReconstructFrame);
+ return false; // don't walk descendants
+ }
+ return true; // walk descendants
+}
+
+static void
+WalkFramesThroughPlaceholders(nsPresContext *aPresContext, nsIFrame *aFrame,
+ frameWalkerFn aFunc, void *aClosure)
+{
+ bool walkChildren = (*aFunc)(aFrame, aClosure);
+ if (!walkChildren)
+ return;
+
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* child = childFrames.get();
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ // only do frames that are in flow, and recur through the
+ // out-of-flows of placeholders.
+ WalkFramesThroughPlaceholders(aPresContext,
+ nsPlaceholderFrame::GetRealFrameFor(child),
+ aFunc, aClosure);
+ }
+ }
+ }
+}
+#endif
+
+NS_IMETHODIMP
+PresShell::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (mIsDestroying) {
+ NS_WARNING("our observers should have been unregistered by now");
+ return NS_OK;
+ }
+
+#ifdef MOZ_XUL
+ if (!nsCRT::strcmp(aTopic, "chrome-flush-skin-caches")) {
+ nsIFrame *rootFrame = mFrameConstructor->GetRootFrame();
+ // Need to null-check because "chrome-flush-skin-caches" can happen
+ // at interesting times during startup.
+ if (rootFrame) {
+ NS_ASSERTION(mViewManager, "View manager must exist");
+
+ nsWeakFrame weakRoot(rootFrame);
+ // Have to make sure that the content notifications are flushed before we
+ // start messing with the frame model; otherwise we can get content doubling.
+ mDocument->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ if (weakRoot.IsAlive()) {
+ WalkFramesThroughPlaceholders(mPresContext, rootFrame,
+ &ReResolveMenusAndTrees, nullptr);
+
+ // Because "chrome:" URL equality is messy, reframe image box
+ // frames (hack!).
+ nsStyleChangeList changeList;
+ WalkFramesThroughPlaceholders(mPresContext, rootFrame,
+ ReframeImageBoxes, &changeList);
+ // Mark ourselves as not safe to flush while we're doing frame
+ // construction.
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ ++mChangeNestCount;
+ RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
+ if (restyleManager->IsServo()) {
+ MOZ_CRASH("stylo: PresShell::Observe(\"chrome-flush-skin-caches\") "
+ "not implemented for Servo-backed style system");
+ }
+ restyleManager->AsGecko()->ProcessRestyledFrames(changeList);
+ restyleManager->AsGecko()->FlushOverflowChangedTracker();
+ --mChangeNestCount;
+ }
+ }
+ }
+ return NS_OK;
+ }
+#endif
+
+ if (!nsCRT::strcmp(aTopic, "agent-sheet-added")) {
+ if (mStyleSet) {
+ AddAgentSheet(aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "user-sheet-added")) {
+ if (mStyleSet) {
+ AddUserSheet(aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "author-sheet-added")) {
+ if (mStyleSet) {
+ AddAuthorSheet(aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "agent-sheet-removed")) {
+ if (mStyleSet) {
+ RemoveSheet(SheetType::Agent, aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "user-sheet-removed")) {
+ if (mStyleSet) {
+ RemoveSheet(SheetType::User, aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "author-sheet-removed")) {
+ if (mStyleSet) {
+ RemoveSheet(SheetType::Doc, aSubject);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ if (!AssumeAllFramesVisible() && mPresContext->IsRootContentDocument()) {
+ DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
+ }
+ return NS_OK;
+ }
+
+ NS_WARNING("unrecognized topic in PresShell::Observe");
+ return NS_ERROR_FAILURE;
+}
+
+bool
+nsIPresShell::AddRefreshObserverInternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ nsPresContext* presContext = GetPresContext();
+ return presContext &&
+ presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType);
+}
+
+/* virtual */ bool
+nsIPresShell::AddRefreshObserverExternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ return AddRefreshObserverInternal(aObserver, aFlushType);
+}
+
+bool
+nsIPresShell::RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ nsPresContext* presContext = GetPresContext();
+ return presContext &&
+ presContext->RefreshDriver()->RemoveRefreshObserver(aObserver, aFlushType);
+}
+
+/* virtual */ bool
+nsIPresShell::RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ return RemoveRefreshObserverInternal(aObserver, aFlushType);
+}
+
+/* virtual */ bool
+nsIPresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return false;
+ }
+ presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
+ return true;
+}
+
+/* virtual */ bool
+nsIPresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return false;
+ }
+ presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
+ return true;
+}
+
+//------------------------------------------------------
+// End of protected and private methods on the PresShell
+//------------------------------------------------------
+
+//------------------------------------------------------------------
+//-- Delayed event Classes Impls
+//------------------------------------------------------------------
+
+PresShell::DelayedInputEvent::DelayedInputEvent() :
+ DelayedEvent(),
+ mEvent(nullptr)
+{
+}
+
+PresShell::DelayedInputEvent::~DelayedInputEvent()
+{
+ delete mEvent;
+}
+
+void
+PresShell::DelayedInputEvent::Dispatch()
+{
+ if (!mEvent || !mEvent->mWidget) {
+ return;
+ }
+ nsCOMPtr<nsIWidget> widget = mEvent->mWidget;
+ nsEventStatus status;
+ widget->DispatchEvent(mEvent, status);
+}
+
+PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent) :
+ DelayedInputEvent()
+{
+ WidgetMouseEvent* mouseEvent =
+ new WidgetMouseEvent(aEvent->IsTrusted(),
+ aEvent->mMessage,
+ aEvent->mWidget,
+ aEvent->mReason,
+ aEvent->mContextMenuTrigger);
+ mouseEvent->AssignMouseEventData(*aEvent, false);
+ mEvent = mouseEvent;
+}
+
+PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent) :
+ DelayedInputEvent()
+{
+ WidgetKeyboardEvent* keyEvent =
+ new WidgetKeyboardEvent(aEvent->IsTrusted(),
+ aEvent->mMessage,
+ aEvent->mWidget);
+ keyEvent->AssignKeyEventData(*aEvent, false);
+ keyEvent->mFlags.mIsSynthesizedForTests = aEvent->mFlags.mIsSynthesizedForTests;
+ mEvent = keyEvent;
+}
+
+// Start of DEBUG only code
+
+#ifdef DEBUG
+
+static void
+LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg)
+{
+ nsAutoString n1, n2;
+ if (k1) {
+ k1->GetFrameName(n1);
+ } else {
+ n1.AssignLiteral(u"(null)");
+ }
+
+ if (k2) {
+ k2->GetFrameName(n2);
+ } else {
+ n2.AssignLiteral(u"(null)");
+ }
+
+ printf("verifyreflow: %s %p != %s %p %s\n",
+ NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1,
+ NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg);
+}
+
+static void
+LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
+ const nsRect& r1, const nsRect& r2)
+{
+ printf("VerifyReflow Error:\n");
+ nsAutoString name;
+
+ if (k1) {
+ k1->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
+ }
+ printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
+
+ if (k2) {
+ k2->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
+ }
+ printf("{%d, %d, %d, %d}\n %s\n",
+ r2.x, r2.y, r2.width, r2.height, aMsg);
+}
+
+static void
+LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
+ const nsIntRect& r1, const nsIntRect& r2)
+{
+ printf("VerifyReflow Error:\n");
+ nsAutoString name;
+
+ if (k1) {
+ k1->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
+ }
+ printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
+
+ if (k2) {
+ k2->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
+ }
+ printf("{%d, %d, %d, %d}\n %s\n",
+ r2.x, r2.y, r2.width, r2.height, aMsg);
+}
+
+static bool
+CompareTrees(nsPresContext* aFirstPresContext, nsIFrame* aFirstFrame,
+ nsPresContext* aSecondPresContext, nsIFrame* aSecondFrame)
+{
+ if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext || !aSecondFrame)
+ return true;
+ // XXX Evil hack to reduce false positives; I can't seem to figure
+ // out how to flush scrollbar changes correctly
+ //if (aFirstFrame->GetType() == nsGkAtoms::scrollbarFrame)
+ // return true;
+ bool ok = true;
+ nsIFrame::ChildListIterator lists1(aFirstFrame);
+ nsIFrame::ChildListIterator lists2(aSecondFrame);
+ do {
+ const nsFrameList& kids1 = !lists1.IsDone() ? lists1.CurrentList() : nsFrameList();
+ const nsFrameList& kids2 = !lists2.IsDone() ? lists2.CurrentList() : nsFrameList();
+ int32_t l1 = kids1.GetLength();
+ int32_t l2 = kids2.GetLength();
+ if (l1 != l2) {
+ ok = false;
+ LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
+ "child counts don't match: ");
+ printf("%d != %d\n", l1, l2);
+ if (0 == (VERIFY_REFLOW_ALL & gVerifyReflowFlags)) {
+ break;
+ }
+ }
+
+ LayoutDeviceIntRect r1, r2;
+ nsView* v1;
+ nsView* v2;
+ for (nsFrameList::Enumerator e1(kids1), e2(kids2);
+ ;
+ e1.Next(), e2.Next()) {
+ nsIFrame* k1 = e1.get();
+ nsIFrame* k2 = e2.get();
+ if (((nullptr == k1) && (nullptr != k2)) ||
+ ((nullptr != k1) && (nullptr == k2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child lists are different\n");
+ break;
+ }
+ else if (nullptr != k1) {
+ // Verify that the frames are the same size
+ if (!k1->GetRect().IsEqualInterior(k2->GetRect())) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(), k2->GetRect());
+ }
+
+ // Make sure either both have views or neither have views; if they
+ // do have views, make sure the views are the same size. If the
+ // views have widgets, make sure they both do or neither does. If
+ // they do, make sure the widgets are the same size.
+ v1 = k1->GetView();
+ v2 = k2->GetView();
+ if (((nullptr == v1) && (nullptr != v2)) ||
+ ((nullptr != v1) && (nullptr == v2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child views are not matched\n");
+ }
+ else if (nullptr != v1) {
+ if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) {
+ LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(), v2->GetBounds());
+ }
+
+ nsIWidget* w1 = v1->GetWidget();
+ nsIWidget* w2 = v2->GetWidget();
+ if (((nullptr == w1) && (nullptr != w2)) ||
+ ((nullptr != w1) && (nullptr == w2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child widgets are not matched\n");
+ }
+ else if (nullptr != w1) {
+ r1 = w1->GetBounds();
+ r2 = w2->GetBounds();
+ if (!r1.IsEqualEdges(r2)) {
+ LogVerifyMessage(k1, k2, "(widget rects)",
+ r1.ToUnknownRect(), r2.ToUnknownRect());
+ }
+ }
+ }
+ if (!ok && (0 == (VERIFY_REFLOW_ALL & gVerifyReflowFlags))) {
+ break;
+ }
+
+ // XXX Should perhaps compare their float managers.
+
+ // Compare the sub-trees too
+ if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) {
+ ok = false;
+ if (0 == (VERIFY_REFLOW_ALL & gVerifyReflowFlags)) {
+ break;
+ }
+ }
+ }
+ else {
+ break;
+ }
+ }
+ if (!ok && (0 == (VERIFY_REFLOW_ALL & gVerifyReflowFlags))) {
+ break;
+ }
+
+ lists1.Next();
+ lists2.Next();
+ if (lists1.IsDone() != lists2.IsDone() ||
+ (!lists1.IsDone() && lists1.CurrentID() != lists2.CurrentID())) {
+ if (0 == (VERIFY_REFLOW_ALL & gVerifyReflowFlags)) {
+ ok = false;
+ }
+ LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
+ "child list names are not matched: ");
+ fprintf(stdout, "%s != %s\n",
+ !lists1.IsDone() ? mozilla::layout::ChildListName(lists1.CurrentID()) : "(null)",
+ !lists2.IsDone() ? mozilla::layout::ChildListName(lists2.CurrentID()) : "(null)");
+ break;
+ }
+ } while (ok && !lists1.IsDone());
+
+ return ok;
+}
+#endif
+
+#if 0
+static nsIFrame*
+FindTopFrame(nsIFrame* aRoot)
+{
+ if (aRoot) {
+ nsIContent* content = aRoot->GetContent();
+ if (content) {
+ nsIAtom* tag;
+ content->GetTag(tag);
+ if (nullptr != tag) {
+ NS_RELEASE(tag);
+ return aRoot;
+ }
+ }
+
+ // Try one of the children
+ for (nsIFrame* kid : aRoot->PrincipalChildList()) {
+ nsIFrame* result = FindTopFrame(kid);
+ if (nullptr != result) {
+ return result;
+ }
+ }
+ }
+ return nullptr;
+}
+#endif
+
+
+#ifdef DEBUG
+
+nsStyleSet*
+PresShell::CloneStyleSet(nsStyleSet* aSet)
+{
+ nsStyleSet* clone = new nsStyleSet();
+
+ int32_t i, n = aSet->SheetCount(SheetType::Override);
+ for (i = 0; i < n; i++) {
+ CSSStyleSheet* ss = aSet->StyleSheetAt(SheetType::Override, i);
+ if (ss)
+ clone->AppendStyleSheet(SheetType::Override, ss);
+ }
+
+ // The document expects to insert document stylesheets itself
+#if 0
+ n = aSet->SheetCount(SheetType::Doc);
+ for (i = 0; i < n; i++) {
+ CSSStyleSheet* ss = aSet->StyleSheetAt(SheetType::Doc, i);
+ if (ss)
+ clone->AddDocStyleSheet(ss, mDocument);
+ }
+#endif
+
+ n = aSet->SheetCount(SheetType::User);
+ for (i = 0; i < n; i++) {
+ CSSStyleSheet* ss = aSet->StyleSheetAt(SheetType::User, i);
+ if (ss)
+ clone->AppendStyleSheet(SheetType::User, ss);
+ }
+
+ n = aSet->SheetCount(SheetType::Agent);
+ for (i = 0; i < n; i++) {
+ CSSStyleSheet* ss = aSet->StyleSheetAt(SheetType::Agent, i);
+ if (ss)
+ clone->AppendStyleSheet(SheetType::Agent, ss);
+ }
+ return clone;
+}
+
+// After an incremental reflow, we verify the correctness by doing a
+// full reflow into a fresh frame tree.
+bool
+PresShell::VerifyIncrementalReflow()
+{
+ if (VERIFY_REFLOW_NOISY & gVerifyReflowFlags) {
+ printf("Building Verification Tree...\n");
+ }
+
+ // Create a presentation context to view the new frame tree
+ RefPtr<nsPresContext> cx =
+ new nsRootPresContext(mDocument, mPresContext->IsPaginated() ?
+ nsPresContext::eContext_PrintPreview :
+ nsPresContext::eContext_Galley);
+ NS_ENSURE_TRUE(cx, false);
+
+ nsDeviceContext *dc = mPresContext->DeviceContext();
+ nsresult rv = cx->Init(dc);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Get our scrolling preference
+ nsView* rootView = mViewManager->GetRootView();
+ NS_ENSURE_TRUE(rootView->HasWidget(), false);
+ nsIWidget* parentWidget = rootView->GetWidget();
+
+ // Create a new view manager.
+ RefPtr<nsViewManager> vm = new nsViewManager();
+ NS_ENSURE_TRUE(vm, false);
+ rv = vm->Init(dc);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Create a child window of the parent that is our "root view/window"
+ // Create a view
+ nsRect tbounds = mPresContext->GetVisibleArea();
+ nsView* view = vm->CreateView(tbounds, nullptr);
+ NS_ENSURE_TRUE(view, false);
+
+ //now create the widget for the view
+ rv = view->CreateWidgetForParent(parentWidget, nullptr, true);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Setup hierarchical relationship in view manager
+ vm->SetRootView(view);
+
+ // Make the new presentation context the same size as our
+ // presentation context.
+ nsRect r = mPresContext->GetVisibleArea();
+ cx->SetVisibleArea(r);
+
+ // Create a new presentation shell to view the document. Use the
+ // exact same style information that this document has.
+ if (mStyleSet->IsServo()) {
+ NS_WARNING("VerifyIncrementalReflow cannot handle ServoStyleSets");
+ return true;
+ }
+ nsAutoPtr<nsStyleSet> newSet(CloneStyleSet(mStyleSet->AsGecko()));
+ nsCOMPtr<nsIPresShell> sh = mDocument->CreateShell(cx, vm, newSet.get());
+ NS_ENSURE_TRUE(sh, false);
+ newSet.forget();
+ // Note that after we create the shell, we must make sure to destroy it
+ sh->SetVerifyReflowEnable(false); // turn off verify reflow while we're reflowing the test frame tree
+ vm->SetPresShell(sh);
+ {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ sh->Initialize(r.width, r.height);
+ }
+ mDocument->BindingManager()->ProcessAttachedQueue();
+ sh->FlushPendingNotifications(Flush_Layout);
+ sh->SetVerifyReflowEnable(true); // turn on verify reflow again now that we're done reflowing the test frame tree
+ // Force the non-primary presshell to unsuppress; it doesn't want to normally
+ // because it thinks it's hidden
+ ((PresShell*)sh.get())->mPaintingSuppressed = false;
+ if (VERIFY_REFLOW_NOISY & gVerifyReflowFlags) {
+ printf("Verification Tree built, comparing...\n");
+ }
+
+ // Now that the document has been reflowed, use its frame tree to
+ // compare against our frame tree.
+ nsIFrame* root1 = mFrameConstructor->GetRootFrame();
+ nsIFrame* root2 = sh->GetRootFrame();
+ bool ok = CompareTrees(mPresContext, root1, cx, root2);
+ if (!ok && (VERIFY_REFLOW_NOISY & gVerifyReflowFlags)) {
+ printf("Verify reflow failed, primary tree:\n");
+ root1->List(stdout, 0);
+ printf("Verification tree:\n");
+ root2->List(stdout, 0);
+ }
+
+#if 0
+ // Sample code for dumping page to png
+ // XXX Needs to be made more flexible
+ if (!ok) {
+ nsString stra;
+ static int num = 0;
+ stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea");
+ stra.AppendInt(num);
+ stra.AppendLiteral(".png");
+ gfxUtils::WriteAsPNG(sh, stra);
+ nsString strb;
+ strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb");
+ strb.AppendInt(num);
+ strb.AppendLiteral(".png");
+ gfxUtils::WriteAsPNG(sh, strb);
+ ++num;
+ }
+#endif
+
+ sh->EndObservingDocument();
+ sh->Destroy();
+ if (VERIFY_REFLOW_NOISY & gVerifyReflowFlags) {
+ printf("Finished Verifying Reflow...\n");
+ }
+
+ return ok;
+}
+
+// Layout debugging hooks
+void
+PresShell::ListStyleContexts(nsIFrame *aRootFrame, FILE *out, int32_t aIndent)
+{
+ nsStyleContext *sc = aRootFrame->StyleContext();
+ if (sc)
+ sc->List(out, aIndent);
+}
+
+void
+PresShell::ListStyleSheets(FILE *out, int32_t aIndent)
+{
+ int32_t sheetCount = mStyleSet->SheetCount(SheetType::Doc);
+ for (int32_t i = 0; i < sheetCount; ++i) {
+ mStyleSet->StyleSheetAt(SheetType::Doc, i)->List(out, aIndent);
+ fputs("\n", out);
+ }
+}
+
+void
+PresShell::VerifyStyleTree()
+{
+ VERIFY_STYLE_TREE;
+}
+#endif
+
+//=============================================================
+//=============================================================
+//-- Debug Reflow Counts
+//=============================================================
+//=============================================================
+#ifdef MOZ_REFLOW_PERF
+//-------------------------------------------------------------
+void
+PresShell::DumpReflows()
+{
+ if (mReflowCountMgr) {
+ nsAutoCString uriStr;
+ if (mDocument) {
+ nsIURI *uri = mDocument->GetDocumentURI();
+ if (uri) {
+ uri->GetPath(uriStr);
+ }
+ }
+ mReflowCountMgr->DisplayTotals(uriStr.get());
+ mReflowCountMgr->DisplayHTMLTotals(uriStr.get());
+ mReflowCountMgr->DisplayDiffsInTotals();
+ }
+}
+
+//-------------------------------------------------------------
+void
+PresShell::CountReflows(const char * aName, nsIFrame * aFrame)
+{
+ if (mReflowCountMgr) {
+ mReflowCountMgr->Add(aName, aFrame);
+ }
+}
+
+//-------------------------------------------------------------
+void
+PresShell::PaintCount(const char * aName,
+ nsRenderingContext* aRenderingContext,
+ nsPresContext* aPresContext,
+ nsIFrame * aFrame,
+ const nsPoint& aOffset,
+ uint32_t aColor)
+{
+ if (mReflowCountMgr) {
+ mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext,
+ aFrame, aOffset, aColor);
+ }
+}
+
+//-------------------------------------------------------------
+void
+PresShell::SetPaintFrameCount(bool aPaintFrameCounts)
+{
+ if (mReflowCountMgr) {
+ mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts);
+ }
+}
+
+bool
+PresShell::IsPaintingFrameCounts()
+{
+ if (mReflowCountMgr)
+ return mReflowCountMgr->IsPaintingFrameCounts();
+ return false;
+}
+
+//------------------------------------------------------------------
+//-- Reflow Counter Classes Impls
+//------------------------------------------------------------------
+
+//------------------------------------------------------------------
+ReflowCounter::ReflowCounter(ReflowCountMgr * aMgr) :
+ mMgr(aMgr)
+{
+ ClearTotals();
+ SetTotalsCache();
+}
+
+//------------------------------------------------------------------
+ReflowCounter::~ReflowCounter()
+{
+
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::ClearTotals()
+{
+ mTotal = 0;
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::SetTotalsCache()
+{
+ mCacheTotal = mTotal;
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::CalcDiffInTotals()
+{
+ mCacheTotal = mTotal - mCacheTotal;
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayTotals(const char * aStr)
+{
+ DisplayTotals(mTotal, aStr?aStr:"Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayDiffTotals(const char * aStr)
+{
+ DisplayTotals(mCacheTotal, aStr?aStr:"Diff Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayHTMLTotals(const char * aStr)
+{
+ DisplayHTMLTotals(mTotal, aStr?aStr:"Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayTotals(uint32_t aTotal, const char * aTitle)
+{
+ // figure total
+ if (aTotal == 0) {
+ return;
+ }
+ ReflowCounter * gTots = (ReflowCounter *)mMgr->LookUp(kGrandTotalsStr);
+
+ printf("%25s\t", aTitle);
+ printf("%d\t", aTotal);
+ if (gTots != this && aTotal > 0) {
+ gTots->Add(aTotal);
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char * aTitle)
+{
+ if (aTotal == 0) {
+ return;
+ }
+
+ ReflowCounter * gTots = (ReflowCounter *)mMgr->LookUp(kGrandTotalsStr);
+ FILE * fd = mMgr->GetOutFile();
+ if (!fd) {
+ return;
+ }
+
+ fprintf(fd, "<tr><td><center>%s</center></td>", aTitle);
+ fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal);
+
+ if (gTots != this && aTotal > 0) {
+ gTots->Add(aTotal);
+ }
+}
+
+//------------------------------------------------------------------
+//-- ReflowCountMgr
+//------------------------------------------------------------------
+
+#define KEY_BUF_SIZE_FOR_PTR 24 // adequate char[] buffer to sprintf a pointer
+
+ReflowCountMgr::ReflowCountMgr()
+{
+ mCounts = PL_NewHashTable(10, PL_HashString, PL_CompareStrings,
+ PL_CompareValues, nullptr, nullptr);
+ mIndiFrameCounts = PL_NewHashTable(10, PL_HashString, PL_CompareStrings,
+ PL_CompareValues, nullptr, nullptr);
+ mCycledOnce = false;
+ mDumpFrameCounts = false;
+ mDumpFrameByFrameCounts = false;
+ mPaintFrameByFrameCounts = false;
+}
+
+//------------------------------------------------------------------
+ReflowCountMgr::~ReflowCountMgr()
+{
+ CleanUp();
+}
+
+//------------------------------------------------------------------
+ReflowCounter * ReflowCountMgr::LookUp(const char * aName)
+{
+ if (nullptr != mCounts) {
+ ReflowCounter * counter = (ReflowCounter *)PL_HashTableLookup(mCounts, aName);
+ return counter;
+ }
+ return nullptr;
+
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::Add(const char * aName, nsIFrame * aFrame)
+{
+ NS_ASSERTION(aName != nullptr, "Name shouldn't be null!");
+
+ if (mDumpFrameCounts && nullptr != mCounts) {
+ ReflowCounter * counter = (ReflowCounter *)PL_HashTableLookup(mCounts, aName);
+ if (counter == nullptr) {
+ counter = new ReflowCounter(this);
+ char * name = NS_strdup(aName);
+ NS_ASSERTION(name != nullptr, "null ptr");
+ PL_HashTableAdd(mCounts, name, counter);
+ }
+ counter->Add();
+ }
+
+ if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) &&
+ nullptr != mIndiFrameCounts &&
+ aFrame != nullptr) {
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aFrame);
+ IndiReflowCounter * counter = (IndiReflowCounter *)PL_HashTableLookup(mIndiFrameCounts, key);
+ if (counter == nullptr) {
+ counter = new IndiReflowCounter(this);
+ counter->mFrame = aFrame;
+ counter->mName.AssignASCII(aName);
+ PL_HashTableAdd(mIndiFrameCounts, NS_strdup(key), counter);
+ }
+ // this eliminates extra counts from super classes
+ if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
+ counter->mCount++;
+ counter->mCounter.Add(1);
+ }
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::PaintCount(const char* aName,
+ nsRenderingContext* aRenderingContext,
+ nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ const nsPoint& aOffset,
+ uint32_t aColor)
+{
+ if (mPaintFrameByFrameCounts &&
+ nullptr != mIndiFrameCounts &&
+ aFrame != nullptr) {
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aFrame);
+ IndiReflowCounter * counter =
+ (IndiReflowCounter *)PL_HashTableLookup(mIndiFrameCounts, key);
+ if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
+ DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
+ int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+
+ aRenderingContext->ThebesContext()->Save();
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
+ aRenderingContext->ThebesContext()->SetMatrix(
+ aRenderingContext->ThebesContext()->CurrentMatrix().Translate(devPixelOffset));
+
+ // We don't care about the document language or user fonts here;
+ // just get a default Latin font.
+ nsFont font(eFamily_serif, nsPresContext::CSSPixelsToAppUnits(11));
+ nsFontMetrics::Params params;
+ params.language = nsGkAtoms::x_western;
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fm =
+ aPresContext->DeviceContext()->GetMetricsFor(font, params);
+
+ char buf[16];
+ int len = SprintfLiteral(buf, "%d", counter->mCount);
+ nscoord x = 0, y = fm->MaxAscent();
+ nscoord width, height = fm->MaxHeight();
+ fm->SetTextRunRTL(false);
+ width = fm->GetWidth(buf, len, drawTarget);
+
+ Color color;
+ Color color2;
+ if (aColor != 0) {
+ color = Color::FromABGR(aColor);
+ color2 = Color(0.f, 0.f, 0.f);
+ } else {
+ gfx::Float rc = 0.f, gc = 0.f, bc = 0.f;
+ if (counter->mCount < 5) {
+ rc = 1.f;
+ gc = 1.f;
+ } else if (counter->mCount < 11) {
+ gc = 1.f;
+ } else {
+ rc = 1.f;
+ }
+ color = Color(rc, gc, bc);
+ color2 = Color(rc/2, gc/2, bc/2);
+ }
+
+ nsRect rect(0,0, width+15, height+15);
+ Rect devPxRect =
+ NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget);
+ ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
+ drawTarget->FillRect(devPxRect, black);
+
+ aRenderingContext->ThebesContext()->SetColor(color2);
+ fm->DrawString(buf, len, x+15, y+15, aRenderingContext);
+ aRenderingContext->ThebesContext()->SetColor(color);
+ fm->DrawString(buf, len, x, y, aRenderingContext);
+
+ aRenderingContext->ThebesContext()->Restore();
+ }
+ }
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::RemoveItems(PLHashEntry *he, int i, void *arg)
+{
+ char *str = (char *)he->key;
+ ReflowCounter * counter = (ReflowCounter *)he->value;
+ delete counter;
+ free(str);
+
+ return HT_ENUMERATE_REMOVE;
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::RemoveIndiItems(PLHashEntry *he, int i, void *arg)
+{
+ char *str = (char *)he->key;
+ IndiReflowCounter * counter = (IndiReflowCounter *)he->value;
+ delete counter;
+ free(str);
+
+ return HT_ENUMERATE_REMOVE;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::CleanUp()
+{
+ if (nullptr != mCounts) {
+ PL_HashTableEnumerateEntries(mCounts, RemoveItems, nullptr);
+ PL_HashTableDestroy(mCounts);
+ mCounts = nullptr;
+ }
+
+ if (nullptr != mIndiFrameCounts) {
+ PL_HashTableEnumerateEntries(mIndiFrameCounts, RemoveIndiItems, nullptr);
+ PL_HashTableDestroy(mIndiFrameCounts);
+ mIndiFrameCounts = nullptr;
+ }
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::DoSingleTotal(PLHashEntry *he, int i, void *arg)
+{
+ char *str = (char *)he->key;
+ ReflowCounter * counter = (ReflowCounter *)he->value;
+
+ counter->DisplayTotals(str);
+
+ return HT_ENUMERATE_NEXT;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoGrandTotals()
+{
+ if (nullptr != mCounts) {
+ ReflowCounter * gTots = (ReflowCounter *)PL_HashTableLookup(mCounts, kGrandTotalsStr);
+ if (gTots == nullptr) {
+ gTots = new ReflowCounter(this);
+ PL_HashTableAdd(mCounts, NS_strdup(kGrandTotalsStr), gTots);
+ } else {
+ gTots->ClearTotals();
+ }
+
+ printf("\t\t\t\tTotal\n");
+ for (uint32_t i=0;i<78;i++) {
+ printf("-");
+ }
+ printf("\n");
+ PL_HashTableEnumerateEntries(mCounts, DoSingleTotal, this);
+ }
+}
+
+static void RecurseIndiTotals(nsPresContext* aPresContext,
+ PLHashTable * aHT,
+ nsIFrame * aParentFrame,
+ int32_t aLevel)
+{
+ if (aParentFrame == nullptr) {
+ return;
+ }
+
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aParentFrame);
+ IndiReflowCounter * counter = (IndiReflowCounter *)PL_HashTableLookup(aHT, key);
+ if (counter) {
+ counter->mHasBeenOutput = true;
+ char * name = ToNewCString(counter->mName);
+ for (int32_t i=0;i<aLevel;i++) printf(" ");
+ printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount);
+ printf("%d", counter->mCounter.GetTotal());
+ printf("]\n");
+ free(name);
+ }
+
+ for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
+ RecurseIndiTotals(aPresContext, aHT, child, aLevel+1);
+ }
+
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::DoSingleIndi(PLHashEntry *he, int i, void *arg)
+{
+ IndiReflowCounter * counter = (IndiReflowCounter *)he->value;
+ if (counter && !counter->mHasBeenOutput) {
+ char * name = ToNewCString(counter->mName);
+ printf("%s - %p [%d][", name, (void*)counter->mFrame, counter->mCount);
+ printf("%d", counter->mCounter.GetTotal());
+ printf("]\n");
+ free(name);
+ }
+ return HT_ENUMERATE_NEXT;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoIndiTotalsTree()
+{
+ if (nullptr != mCounts) {
+ printf("\n------------------------------------------------\n");
+ printf("-- Individual Frame Counts\n");
+ printf("------------------------------------------------\n");
+
+ if (mPresShell) {
+ nsIFrame * rootFrame = mPresShell->FrameManager()->GetRootFrame();
+ RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0);
+ printf("------------------------------------------------\n");
+ printf("-- Individual Counts of Frames not in Root Tree\n");
+ printf("------------------------------------------------\n");
+ PL_HashTableEnumerateEntries(mIndiFrameCounts, DoSingleIndi, this);
+ }
+ }
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::DoSingleHTMLTotal(PLHashEntry *he, int i, void *arg)
+{
+ char *str = (char *)he->key;
+ ReflowCounter * counter = (ReflowCounter *)he->value;
+
+ counter->DisplayHTMLTotals(str);
+
+ return HT_ENUMERATE_NEXT;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoGrandHTMLTotals()
+{
+ if (nullptr != mCounts) {
+ ReflowCounter * gTots = (ReflowCounter *)PL_HashTableLookup(mCounts, kGrandTotalsStr);
+ if (gTots == nullptr) {
+ gTots = new ReflowCounter(this);
+ PL_HashTableAdd(mCounts, NS_strdup(kGrandTotalsStr), gTots);
+ } else {
+ gTots->ClearTotals();
+ }
+
+ static const char * title[] = {"Class", "Reflows"};
+ fprintf(mFD, "<tr>");
+ for (uint32_t i=0; i < ArrayLength(title); i++) {
+ fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]);
+ }
+ fprintf(mFD, "</tr>\n");
+ PL_HashTableEnumerateEntries(mCounts, DoSingleHTMLTotal, this);
+ }
+}
+
+//------------------------------------
+void ReflowCountMgr::DisplayTotals(const char * aStr)
+{
+#ifdef DEBUG_rods
+ printf("%s\n", aStr?aStr:"No name");
+#endif
+ if (mDumpFrameCounts) {
+ DoGrandTotals();
+ }
+ if (mDumpFrameByFrameCounts) {
+ DoIndiTotalsTree();
+ }
+
+}
+//------------------------------------
+void ReflowCountMgr::DisplayHTMLTotals(const char * aStr)
+{
+#ifdef WIN32x // XXX NOT XP!
+ char name[1024];
+
+ char * sptr = strrchr(aStr, '/');
+ if (sptr) {
+ sptr++;
+ strcpy(name, sptr);
+ char * eptr = strrchr(name, '.');
+ if (eptr) {
+ *eptr = 0;
+ }
+ strcat(name, "_stats.html");
+ }
+ mFD = fopen(name, "w");
+ if (mFD) {
+ fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n");
+ const char * title = aStr?aStr:"No name";
+ fprintf(mFD, "<center><b>%s</b><br><table border=1 style=\"background-color:#e0e0e0\">", title);
+ DoGrandHTMLTotals();
+ fprintf(mFD, "</center></table>\n");
+ fprintf(mFD, "</body></html>\n");
+ fclose(mFD);
+ mFD = nullptr;
+ }
+#endif // not XP!
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::DoClearTotals(PLHashEntry *he, int i, void *arg)
+{
+ ReflowCounter * counter = (ReflowCounter *)he->value;
+ counter->ClearTotals();
+
+ return HT_ENUMERATE_NEXT;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::ClearTotals()
+{
+ PL_HashTableEnumerateEntries(mCounts, DoClearTotals, this);
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::ClearGrandTotals()
+{
+ if (nullptr != mCounts) {
+ ReflowCounter * gTots = (ReflowCounter *)PL_HashTableLookup(mCounts, kGrandTotalsStr);
+ if (gTots == nullptr) {
+ gTots = new ReflowCounter(this);
+ PL_HashTableAdd(mCounts, NS_strdup(kGrandTotalsStr), gTots);
+ } else {
+ gTots->ClearTotals();
+ gTots->SetTotalsCache();
+ }
+ }
+}
+
+//------------------------------------------------------------------
+int ReflowCountMgr::DoDisplayDiffTotals(PLHashEntry *he, int i, void *arg)
+{
+ bool cycledOnce = (arg != 0);
+
+ char *str = (char *)he->key;
+ ReflowCounter * counter = (ReflowCounter *)he->value;
+
+ if (cycledOnce) {
+ counter->CalcDiffInTotals();
+ counter->DisplayDiffTotals(str);
+ }
+ counter->SetTotalsCache();
+
+ return HT_ENUMERATE_NEXT;
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DisplayDiffsInTotals()
+{
+ if (mCycledOnce) {
+ printf("Differences\n");
+ for (int32_t i=0;i<78;i++) {
+ printf("-");
+ }
+ printf("\n");
+ ClearGrandTotals();
+ }
+ PL_HashTableEnumerateEntries(mCounts, DoDisplayDiffTotals, (void *)mCycledOnce);
+
+ mCycledOnce = true;
+}
+
+#endif // MOZ_REFLOW_PERF
+
+nsIFrame* nsIPresShell::GetAbsoluteContainingBlock(nsIFrame *aFrame)
+{
+ return FrameConstructor()->GetAbsoluteContainingBlock(aFrame,
+ nsCSSFrameConstructor::ABS_POS);
+}
+
+#ifdef ACCESSIBILITY
+bool
+nsIPresShell::IsAccessibilityActive()
+{
+ return GetAccService() != nullptr;
+}
+
+nsAccessibilityService*
+nsIPresShell::AccService()
+{
+ return GetAccService();
+}
+#endif
+
+void nsIPresShell::InitializeStatics()
+{
+ MOZ_ASSERT(!sPointerCaptureList, "InitializeStatics called multiple times!");
+ sPointerCaptureList =
+ new nsClassHashtable<nsUint32HashKey, PointerCaptureInfo>;
+ sActivePointersIds = new nsClassHashtable<nsUint32HashKey, PointerInfo>;
+}
+
+void nsIPresShell::ReleaseStatics()
+{
+ MOZ_ASSERT(sPointerCaptureList, "ReleaseStatics called without Initialize!");
+ delete sPointerCaptureList;
+ sPointerCaptureList = nullptr;
+ delete sActivePointersIds;
+ sActivePointersIds = nullptr;
+}
+
+// Asks our docshell whether we're active.
+void PresShell::QueryIsActive()
+{
+ nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
+ if (mDocument) {
+ nsIDocument* displayDoc = mDocument->GetDisplayDocument();
+ if (displayDoc) {
+ // Ok, we're an external resource document -- we need to use our display
+ // document's docshell to determine "IsActive" status, since we lack
+ // a container.
+ MOZ_ASSERT(!container,
+ "external resource doc shouldn't have its own container");
+
+ nsIPresShell* displayPresShell = displayDoc->GetShell();
+ if (displayPresShell) {
+ container = displayPresShell->GetPresContext()->GetContainerWeak();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDocShell> docshell(do_QueryInterface(container));
+ if (docshell) {
+ bool isActive;
+ nsresult rv = docshell->GetIsActive(&isActive);
+ // Even though in theory the docshell here could be "Inactive and
+ // Foreground", thus implying aIsHidden=false for SetIsActive(),
+ // this is a newly created PresShell so we'd like to invalidate anyway
+ // upon being made active to ensure that the contents get painted.
+ if (NS_SUCCEEDED(rv))
+ SetIsActive(isActive);
+ }
+}
+
+// Helper for propagating mIsActive changes to external resources
+static bool
+SetExternalResourceIsActive(nsIDocument* aDocument, void* aClosure)
+{
+ nsIPresShell* shell = aDocument->GetShell();
+ if (shell) {
+ shell->SetIsActive(*static_cast<bool*>(aClosure));
+ }
+ return true;
+}
+
+static void
+SetPluginIsActive(nsISupports* aSupports, void* aClosure)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
+ if (!content) {
+ return;
+ }
+
+ nsIFrame *frame = content->GetPrimaryFrame();
+ nsIObjectFrame *objectFrame = do_QueryFrame(frame);
+ if (objectFrame) {
+ objectFrame->SetIsDocumentActive(*static_cast<bool*>(aClosure));
+ }
+}
+
+nsresult
+PresShell::SetIsActive(bool aIsActive)
+{
+ NS_PRECONDITION(mDocument, "should only be called with a document");
+
+ mIsActive = aIsActive;
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext &&
+ presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->SetThrottled(!mIsActive);
+ }
+
+ // Propagate state-change to my resource documents' PresShells
+ mDocument->EnumerateExternalResources(SetExternalResourceIsActive,
+ &aIsActive);
+ mDocument->EnumerateActivityObservers(SetPluginIsActive,
+ &aIsActive);
+ nsresult rv = UpdateImageLockingState();
+#ifdef ACCESSIBILITY
+ if (aIsActive) {
+ nsAccessibilityService* accService = AccService();
+ if (accService) {
+ accService->PresShellActivated(this);
+ }
+ }
+#endif
+ return rv;
+}
+
+/*
+ * Determines the current image locking state. Called when one of the
+ * dependent factors changes.
+ */
+nsresult
+PresShell::UpdateImageLockingState()
+{
+ // We're locked if we're both thawed and active.
+ bool locked = !mFrozen && mIsActive;
+
+ nsresult rv = mDocument->ImageTracker()->SetLockingState(locked);
+
+ if (locked) {
+ // Request decodes for visible image frames; we want to start decoding as
+ // quickly as possible when we get foregrounded to minimize flashing.
+ for (auto iter = mApproximatelyVisibleFrames.Iter(); !iter.Done(); iter.Next()) {
+ nsImageFrame* imageFrame = do_QueryFrame(iter.Get()->GetKey());
+ if (imageFrame) {
+ imageFrame->MaybeDecodeForPredictedSize();
+ }
+ }
+ }
+
+ return rv;
+}
+
+PresShell*
+PresShell::GetRootPresShell()
+{
+ if (mPresContext) {
+ nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext) {
+ return static_cast<PresShell*>(rootPresContext->PresShell());
+ }
+ }
+ return nullptr;
+}
+
+void
+PresShell::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ nsArenaMemoryStats *aArenaObjectsSize,
+ size_t *aPresShellSize,
+ size_t *aStyleSetsSize,
+ size_t *aTextRunsSize,
+ size_t *aPresContextSize)
+{
+ mFrameArena.AddSizeOfExcludingThis(aMallocSizeOf, aArenaObjectsSize);
+ *aPresShellSize += aMallocSizeOf(this);
+ if (mCaret) {
+ *aPresShellSize += mCaret->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ *aPresShellSize += mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ *aPresShellSize += mFramesToDirty.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ *aPresShellSize += aArenaObjectsSize->mOther;
+
+ if (nsStyleSet* styleSet = StyleSet()->GetAsGecko()) {
+ *aStyleSetsSize += styleSet->SizeOfIncludingThis(aMallocSizeOf);
+ } else {
+ NS_WARNING("ServoStyleSets do not support memory measurements yet");
+ }
+
+ *aTextRunsSize += SizeOfTextRuns(aMallocSizeOf);
+
+ *aPresContextSize += mPresContext->SizeOfIncludingThis(aMallocSizeOf);
+}
+
+size_t
+PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const
+{
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) {
+ return 0;
+ }
+
+ // clear the TEXT_RUN_MEMORY_ACCOUNTED flags
+ nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr,
+ /* clear = */true);
+
+ // collect the total memory in use for textruns
+ return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf,
+ /* clear = */false);
+}
+
+void
+nsIPresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty)
+{
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (rootFrame) {
+ const nsFrameList& childList = rootFrame->GetChildList(nsIFrame::kFixedList);
+ for (nsIFrame* childFrame : childList) {
+ FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
+ }
+ }
+}
+
+void
+nsIPresShell::SetScrollPositionClampingScrollPortSize(nscoord aWidth, nscoord aHeight)
+{
+ if (!mScrollPositionClampingScrollPortSizeSet ||
+ mScrollPositionClampingScrollPortSize.width != aWidth ||
+ mScrollPositionClampingScrollPortSize.height != aHeight) {
+ mScrollPositionClampingScrollPortSizeSet = true;
+ mScrollPositionClampingScrollPortSize.width = aWidth;
+ mScrollPositionClampingScrollPortSize.height = aHeight;
+
+ if (nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable()) {
+ rootScrollFrame->MarkScrollbarsDirtyForReflow();
+ }
+ MarkFixedFramesForReflow(nsIPresShell::eResize);
+ }
+}
+
+void
+PresShell::SetupFontInflation()
+{
+ mFontSizeInflationEmPerLine = nsLayoutUtils::FontSizeInflationEmPerLine();
+ mFontSizeInflationMinTwips = nsLayoutUtils::FontSizeInflationMinTwips();
+ mFontSizeInflationLineThreshold = nsLayoutUtils::FontSizeInflationLineThreshold();
+ mFontSizeInflationForceEnabled = nsLayoutUtils::FontSizeInflationForceEnabled();
+ mFontSizeInflationDisabledInMasterProcess = nsLayoutUtils::FontSizeInflationDisabledInMasterProcess();
+
+ NotifyFontSizeInflationEnabledIsDirty();
+}
+
+void
+nsIPresShell::RecomputeFontSizeInflationEnabled()
+{
+ mFontSizeInflationEnabledIsDirty = false;
+
+ MOZ_ASSERT(mPresContext, "our pres context should not be null");
+ if ((FontSizeInflationEmPerLine() == 0 &&
+ FontSizeInflationMinTwips() == 0) || mPresContext->IsChrome()) {
+ mFontSizeInflationEnabled = false;
+ return;
+ }
+
+ // Force-enabling font inflation always trumps the heuristics here.
+ if (!FontSizeInflationForceEnabled()) {
+ if (TabChild* tab = TabChild::GetFrom(this)) {
+ // We're in a child process. Cancel inflation if we're not
+ // async-pan zoomed.
+ if (!tab->AsyncPanZoomEnabled()) {
+ mFontSizeInflationEnabled = false;
+ return;
+ }
+ } else if (XRE_IsParentProcess()) {
+ // We're in the master process. Cancel inflation if it's been
+ // explicitly disabled.
+ if (FontSizeInflationDisabledInMasterProcess()) {
+ mFontSizeInflationEnabled = false;
+ return;
+ }
+ }
+ }
+
+ // XXXjwir3:
+ // See bug 706918, comment 23 for more information on this particular section
+ // of the code. We're using "screen size" in place of the size of the content
+ // area, because on mobile, these are close or equal. This will work for our
+ // purposes (bug 706198), but it will need to be changed in the future to be
+ // more correct when we bring the rest of the viewport code into platform.
+ // We actually want the size of the content area, in the event that we don't
+ // have any metadata about the width and/or height. On mobile, the screen size
+ // and the size of the content area are very close, or the same value.
+ // In XUL fennec, the content area is the size of the <browser> widget, but
+ // in native fennec, the content area is the size of the Gecko LayerView
+ // object.
+
+ // TODO:
+ // Once bug 716575 has been resolved, this code should be changed so that it
+ // does the right thing on all platforms.
+ nsresult rv;
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1", &rv);
+ if (!NS_SUCCEEDED(rv)) {
+ mFontSizeInflationEnabled = false;
+ return;
+ }
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
+ if (screen) {
+ int32_t screenLeft, screenTop, screenWidth, screenHeight;
+ screen->GetRect(&screenLeft, &screenTop, &screenWidth, &screenHeight);
+
+ nsViewportInfo vInf =
+ GetDocument()->GetViewportInfo(ScreenIntSize(screenWidth, screenHeight));
+
+ if (vInf.GetDefaultZoom() >= CSSToScreenScale(1.0f) || vInf.IsAutoSizeEnabled()) {
+ mFontSizeInflationEnabled = false;
+ return;
+ }
+ }
+
+ mFontSizeInflationEnabled = true;
+}
+
+bool
+nsIPresShell::FontSizeInflationEnabled()
+{
+ if (mFontSizeInflationEnabledIsDirty) {
+ RecomputeFontSizeInflationEnabled();
+ }
+
+ return mFontSizeInflationEnabled;
+}
+
+void
+PresShell::PausePainting()
+{
+ if (GetPresContext()->RefreshDriver()->GetPresContext() != GetPresContext())
+ return;
+
+ mPaintingIsFrozen = true;
+ GetPresContext()->RefreshDriver()->Freeze();
+}
+
+void
+PresShell::ResumePainting()
+{
+ if (GetPresContext()->RefreshDriver()->GetPresContext() != GetPresContext())
+ return;
+
+ mPaintingIsFrozen = false;
+ GetPresContext()->RefreshDriver()->Thaw();
+}
+
+void
+nsIPresShell::SyncWindowProperties(nsView* aView)
+{
+ nsIFrame* frame = aView->GetFrame();
+ if (frame && mPresContext) {
+ // CreateReferenceRenderingContext can return nullptr
+ nsRenderingContext rcx(CreateReferenceRenderingContext());
+ nsContainerFrame::SyncWindowProperties(mPresContext, frame, aView, &rcx, 0);
+ }
+}
+
+nsresult
+nsIPresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal)
+{
+ SheetType type;
+ switch (aSheetType) {
+ case nsIStyleSheetService::AGENT_SHEET:
+ type = SheetType::Agent;
+ break;
+ case nsIStyleSheetService::USER_SHEET:
+ type = SheetType::User;
+ break;
+ case nsIStyleSheetService::AUTHOR_SHEET:
+ type = SheetType::Doc;
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected aSheetType value");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ *aRetVal = false;
+ if (nsStyleSet* styleSet = mStyleSet->GetAsGecko()) {
+ // ServoStyleSets do not have rule processors.
+ *aRetVal = styleSet->HasRuleProcessorUsedByMultipleStyleSets(type);
+ }
+ return NS_OK;
+}
diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h
new file mode 100644
index 000000000..ad4ede08b
--- /dev/null
+++ b/layout/base/nsPresShell.h
@@ -0,0 +1,966 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 05/03/2000 IBM Corp. Observer events for reflow states
+ */
+
+/* a presentation of a document, part 2 */
+
+#ifndef nsPresShell_h_
+#define nsPresShell_h_
+
+#include "nsIPresShell.h"
+#include "nsStubDocumentObserver.h"
+#include "nsISelectionController.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsCRT.h"
+#include "nsAutoPtr.h"
+#include "nsIWidget.h"
+#include "nsContentUtils.h" // For AddScriptBlocker().
+#include "nsRefreshDriver.h"
+#include "TouchManager.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/UniquePtr.h"
+#include "MobileViewportManager.h"
+#include "ZoomConstraintsClient.h"
+
+class nsIDocShell;
+class nsRange;
+
+struct RangePaintInfo;
+struct nsCallbackEventRequest;
+#ifdef MOZ_REFLOW_PERF
+class ReflowCountMgr;
+#endif
+
+class nsPresShellEventCB;
+class nsAutoCauseReflowNotifier;
+
+namespace mozilla {
+
+class EventDispatchingCallback;
+
+// A set type for tracking visible frames, for use by the visibility code in
+// PresShell. The set contains nsIFrame* pointers.
+typedef nsTHashtable<nsPtrHashKey<nsIFrame>> VisibleFrames;
+
+// A hash table type for tracking visible regions, for use by the visibility
+// code in PresShell. The mapping is from view IDs to regions in the
+// coordinate system of that view's scrolled frame.
+typedef nsClassHashtable<nsUint64HashKey, mozilla::CSSIntRegion> VisibleRegions;
+
+} // namespace mozilla
+
+// 250ms. This is actually pref-controlled, but we use this value if we fail
+// to get the pref for any reason.
+#define PAINTLOCK_EVENT_DELAY 250
+
+class PresShell final : public nsIPresShell,
+ public nsStubDocumentObserver,
+ public nsISelectionController,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+ template <typename T> using Maybe = mozilla::Maybe<T>;
+ using Nothing = mozilla::Nothing;
+ using OnNonvisible = mozilla::OnNonvisible;
+ using RawSelectionType = mozilla::RawSelectionType;
+ using SelectionType = mozilla::SelectionType;
+ using VisibleFrames = mozilla::VisibleFrames;
+ using VisibleRegions = mozilla::VisibleRegions;
+
+public:
+ PresShell();
+
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ static bool AccessibleCaretEnabled(nsIDocShell* aDocShell);
+
+ // BeforeAfterKeyboardEvent preference
+ static bool BeforeAfterKeyboardEventEnabled();
+
+ static bool IsTargetIframe(nsINode* aTarget);
+
+ void Init(nsIDocument* aDocument, nsPresContext* aPresContext,
+ nsViewManager* aViewManager, mozilla::StyleSetHandle aStyleSet);
+ virtual void Destroy() override;
+ virtual void MakeZombie() override;
+
+ virtual void UpdatePreferenceStyles() override;
+
+ NS_IMETHOD GetSelection(RawSelectionType aRawSelectionType,
+ nsISelection** aSelection) override;
+ virtual mozilla::dom::Selection*
+ GetCurrentSelection(SelectionType aSelectionType) override;
+ virtual already_AddRefed<nsISelectionController>
+ GetSelectionControllerForFocusedContent(
+ nsIContent** aFocusedContent = nullptr) override;
+
+ NS_IMETHOD SetDisplaySelection(int16_t aToggle) override;
+ NS_IMETHOD GetDisplaySelection(int16_t *aToggle) override;
+ NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
+ SelectionRegion aRegion,
+ int16_t aFlags) override;
+ NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
+
+ virtual void BeginObservingDocument() override;
+ virtual void EndObservingDocument() override;
+ virtual nsresult Initialize(nscoord aWidth, nscoord aHeight) override;
+ virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth = 0, nscoord aOldHeight = 0) override;
+ virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight) override;
+ virtual nsIPageSequenceFrame* GetPageSequenceFrame() const override;
+ virtual nsCanvasFrame* GetCanvasFrame() const override;
+
+ virtual nsIFrame* GetPlaceholderFrameFor(nsIFrame* aFrame) const override;
+ virtual void FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd,
+ ReflowRootHandling aRootHandling =
+ eInferFromBitToAdd) override;
+ virtual void FrameNeedsToContinueReflow(nsIFrame *aFrame) override;
+ virtual void CancelAllPendingReflows() override;
+ virtual bool IsSafeToFlush() const override;
+ virtual void FlushPendingNotifications(mozFlushType aType) override;
+ virtual void FlushPendingNotifications(mozilla::ChangesToFlush aType) override;
+ virtual void DestroyFramesFor(nsIContent* aContent,
+ nsIContent** aDestroyedFramesFor) override;
+ virtual void CreateFramesFor(nsIContent* aContent) override;
+
+ /**
+ * Recreates the frames for a node
+ */
+ virtual nsresult RecreateFramesFor(nsIContent* aContent) override;
+
+ /**
+ * Post a callback that should be handled after reflow has finished.
+ */
+ virtual nsresult PostReflowCallback(nsIReflowCallback* aCallback) override;
+ virtual void CancelReflowCallback(nsIReflowCallback* aCallback) override;
+
+ virtual void ClearFrameRefs(nsIFrame* aFrame) override;
+ virtual already_AddRefed<gfxContext> CreateReferenceRenderingContext() override;
+ virtual nsresult GoToAnchor(const nsAString& aAnchorName, bool aScroll,
+ uint32_t aAdditionalScrollFlags = 0) override;
+ virtual nsresult ScrollToAnchor() override;
+
+ virtual nsresult ScrollContentIntoView(nsIContent* aContent,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ uint32_t aFlags) override;
+ virtual bool ScrollFrameRectIntoView(nsIFrame* aFrame,
+ const nsRect& aRect,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ uint32_t aFlags) override;
+ virtual nsRectVisibility GetRectVisibility(nsIFrame *aFrame,
+ const nsRect &aRect,
+ nscoord aMinTwips) const override;
+
+ virtual void SetIgnoreFrameDestruction(bool aIgnore) override;
+ virtual void NotifyDestroyingFrame(nsIFrame* aFrame) override;
+
+ virtual nsresult CaptureHistoryState(nsILayoutHistoryState** aLayoutHistoryState) override;
+
+ virtual void UnsuppressPainting() override;
+
+ virtual nsresult GetAgentStyleSheets(
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets) override;
+ virtual nsresult SetAgentStyleSheets(
+ const nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets) override;
+
+ virtual nsresult AddOverrideStyleSheet(mozilla::StyleSheet* aSheet) override;
+ virtual nsresult RemoveOverrideStyleSheet(mozilla::StyleSheet* aSheet) override;
+
+ virtual nsresult HandleEventWithTarget(
+ mozilla::WidgetEvent* aEvent,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsEventStatus* aStatus) override;
+ virtual nsIFrame* GetEventTargetFrame() override;
+ virtual already_AddRefed<nsIContent> GetEventTargetContent(
+ mozilla::WidgetEvent* aEvent) override;
+
+ virtual void NotifyCounterStylesAreDirty() override;
+
+ virtual nsresult ReconstructFrames(void) override;
+ virtual void Freeze() override;
+ virtual void Thaw() override;
+ virtual void FireOrClearDelayedEvents(bool aFireEvents) override;
+
+ virtual nsresult RenderDocument(const nsRect& aRect, uint32_t aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aThebesContext) override;
+
+ virtual already_AddRefed<SourceSurface>
+ RenderNode(nsIDOMNode* aNode,
+ nsIntRegion* aRegion,
+ const mozilla::LayoutDeviceIntPoint aPoint,
+ mozilla::LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags) override;
+
+ virtual already_AddRefed<SourceSurface>
+ RenderSelection(nsISelection* aSelection,
+ const mozilla::LayoutDeviceIntPoint aPoint,
+ mozilla::LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags) override;
+
+ virtual already_AddRefed<nsPIDOMWindowOuter> GetRootWindow() override;
+
+ virtual LayerManager* GetLayerManager() override;
+
+ virtual bool AsyncPanZoomEnabled() override;
+
+ virtual void SetIgnoreViewportScrolling(bool aIgnore) override;
+
+ virtual nsresult SetResolution(float aResolution) override {
+ return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
+ }
+ virtual nsresult SetResolutionAndScaleTo(float aResolution) override {
+ return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true);
+ }
+ virtual bool ScaleToResolution() const override;
+ virtual float GetCumulativeResolution() override;
+ virtual float GetCumulativeNonRootScaleResolution() override;
+ virtual void SetRestoreResolution(float aResolution,
+ mozilla::LayoutDeviceIntSize aDisplaySize) override;
+
+ //nsIViewObserver interface
+
+ virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
+ uint32_t aFlags) override;
+ virtual nsresult HandleEvent(nsIFrame* aFrame,
+ mozilla::WidgetGUIEvent* aEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus,
+ nsIContent** aTargetContent) override;
+ virtual nsresult HandleDOMEventWithTarget(
+ nsIContent* aTargetContent,
+ mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus) override;
+ virtual nsresult HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ nsIDOMEvent* aEvent,
+ nsEventStatus* aStatus) override;
+ virtual bool ShouldIgnoreInvalidation() override;
+ virtual void WillPaint() override;
+ virtual void WillPaintWindow() override;
+ virtual void DidPaintWindow() override;
+ virtual void ScheduleViewManagerFlush(PaintType aType = PAINT_DEFAULT) override;
+ virtual void DispatchSynthMouseMove(mozilla::WidgetGUIEvent* aEvent,
+ bool aFlushOnHoverChange) override;
+ virtual void ClearMouseCaptureOnView(nsView* aView) override;
+ virtual bool IsVisible() override;
+
+ virtual already_AddRefed<mozilla::AccessibleCaretEventHub> GetAccessibleCaretEventHub() const override;
+
+ // caret handling
+ virtual already_AddRefed<nsCaret> GetCaret() const override;
+ NS_IMETHOD SetCaretEnabled(bool aInEnable) override;
+ NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
+ NS_IMETHOD GetCaretEnabled(bool *aOutEnabled) override;
+ NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
+ NS_IMETHOD GetCaretVisible(bool *_retval) override;
+ virtual void SetCaret(nsCaret *aNewCaret) override;
+ virtual void RestoreCaret() override;
+
+ NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
+ NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override;
+
+ // nsISelectionController
+
+ NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override;
+ NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD CharacterExtendForDelete() override;
+ NS_IMETHOD CharacterExtendForBackspace() override;
+ NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD WordExtendForDelete(bool aForward) override;
+ NS_IMETHOD LineMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD ScrollPage(bool aForward) override;
+ NS_IMETHOD ScrollLine(bool aForward) override;
+ NS_IMETHOD ScrollCharacter(bool aRight) override;
+ NS_IMETHOD CompleteScroll(bool aForward) override;
+ NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD SelectAll() override;
+ NS_IMETHOD CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool *_retval) override;
+ virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset,
+ int16_t aEndOffset, bool* aRetval) override;
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD
+ NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED
+ NS_DECL_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_NSIOBSERVER
+
+#ifdef MOZ_REFLOW_PERF
+ virtual void DumpReflows() override;
+ virtual void CountReflows(const char * aName, nsIFrame * aFrame) override;
+ virtual void PaintCount(const char * aName,
+ nsRenderingContext* aRenderingContext,
+ nsPresContext* aPresContext,
+ nsIFrame * aFrame,
+ const nsPoint& aOffset,
+ uint32_t aColor) override;
+ virtual void SetPaintFrameCount(bool aOn) override;
+ virtual bool IsPaintingFrameCounts() override;
+#endif
+
+#ifdef DEBUG
+ virtual void ListStyleContexts(nsIFrame *aRootFrame, FILE *out,
+ int32_t aIndent = 0) override;
+
+ virtual void ListStyleSheets(FILE *out, int32_t aIndent = 0) override;
+ virtual void VerifyStyleTree() override;
+#endif
+
+ static mozilla::LazyLogModule gLog;
+
+ virtual void DisableNonTestMouseEvents(bool aDisable) override;
+
+ virtual void UpdateCanvasBackground() override;
+
+ virtual void AddCanvasBackgroundColorItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds,
+ nscolor aBackstopColor,
+ uint32_t aFlags) override;
+
+ virtual void AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
+ nsDisplayList& aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds) override;
+
+ virtual nscolor ComputeBackstopColor(nsView* aDisplayRoot) override;
+
+ virtual nsresult SetIsActive(bool aIsActive) override;
+
+ virtual bool GetIsViewportOverridden() override {
+ return (mMobileViewportManager != nullptr);
+ }
+
+ virtual bool IsLayoutFlushObserver() override
+ {
+ return GetPresContext()->RefreshDriver()->
+ IsLayoutFlushObserver(this);
+ }
+
+ virtual void LoadComplete() override;
+
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ nsArenaMemoryStats *aArenaObjectsSize,
+ size_t *aPresShellSize,
+ size_t *aStyleSetsSize,
+ size_t *aTextRunsSize,
+ size_t *aPresContextSize) override;
+ size_t SizeOfTextRuns(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ virtual void AddInvalidateHiddenPresShellObserver(nsRefreshDriver *aDriver) override;
+
+ // This data is stored as a content property (nsGkAtoms::scrolling) on
+ // mContentToScrollTo when we have a pending ScrollIntoView.
+ struct ScrollIntoViewData {
+ ScrollAxis mContentScrollVAxis;
+ ScrollAxis mContentScrollHAxis;
+ uint32_t mContentToScrollToFlags;
+ };
+
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Approximate frame visibility tracking public API.
+ //////////////////////////////////////////////////////////////////////////////
+
+ void ScheduleApproximateFrameVisibilityUpdateSoon() override;
+ void ScheduleApproximateFrameVisibilityUpdateNow() override;
+
+ void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) override;
+ void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr,
+ bool aRemoveOnly = false) override;
+
+ void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) override;
+ void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) override;
+
+ bool AssumeAllFramesVisible() override;
+
+
+ virtual void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) override;
+
+ virtual void DispatchAfterKeyboardEvent(nsINode* aTarget,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled) override;
+
+ virtual bool CanDispatchEvent(
+ const mozilla::WidgetGUIEvent* aEvent = nullptr) const override;
+
+ void SetNextPaintCompressed() { mNextPaintCompressed = true; }
+
+protected:
+ virtual ~PresShell();
+
+ void HandlePostedReflowCallbacks(bool aInterruptible);
+ void CancelPostedReflowCallbacks();
+
+ void ScheduleBeforeFirstPaint();
+ void UnsuppressAndInvalidate();
+
+ void WillCauseReflow() {
+ nsContentUtils::AddScriptBlocker();
+ ++mChangeNestCount;
+ }
+ nsresult DidCauseReflow();
+ friend class nsAutoCauseReflowNotifier;
+
+ nsresult DispatchEventToDOM(mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ nsPresShellEventCB* aEventCB);
+ void DispatchTouchEventToDOM(mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ nsPresShellEventCB* aEventCB,
+ bool aTouchIsNew);
+
+ void WillDoReflow();
+
+ /**
+ * Callback handler for whether reflow happened.
+ *
+ * @param aInterruptible Whether or not reflow interruption is allowed.
+ */
+ void DidDoReflow(bool aInterruptible);
+ // ProcessReflowCommands returns whether we processed all our dirty roots
+ // without interruptions.
+ bool ProcessReflowCommands(bool aInterruptible);
+ // MaybeScheduleReflow checks if posting a reflow is needed, then checks if
+ // the last reflow was interrupted. In the interrupted case ScheduleReflow is
+ // called off a timer, otherwise it is called directly.
+ void MaybeScheduleReflow();
+ // Actually schedules a reflow. This should only be called by
+ // MaybeScheduleReflow and the reflow timer ScheduleReflowOffTimer
+ // sets up.
+ void ScheduleReflow();
+
+ // DoReflow returns whether the reflow finished without interruption
+ bool DoReflow(nsIFrame* aFrame, bool aInterruptible);
+#ifdef DEBUG
+ void DoVerifyReflow();
+ void VerifyHasDirtyRootAncestor(nsIFrame* aFrame);
+#endif
+
+ // Helper for ScrollContentIntoView
+ void DoScrollContentIntoView();
+
+ /**
+ * Initialize cached font inflation preference values and do an initial
+ * computation to determine if font inflation is enabled.
+ *
+ * @see nsLayoutUtils::sFontSizeInflationEmPerLine
+ * @see nsLayoutUtils::sFontSizeInflationMinTwips
+ * @see nsLayoutUtils::sFontSizeInflationLineThreshold
+ */
+ void SetupFontInflation();
+
+ friend struct AutoRenderingStateSaveRestore;
+ friend struct RenderingState;
+
+ struct RenderingState {
+ explicit RenderingState(PresShell* aPresShell)
+ : mResolution(aPresShell->mResolution)
+ , mRenderFlags(aPresShell->mRenderFlags)
+ { }
+ Maybe<float> mResolution;
+ RenderFlags mRenderFlags;
+ };
+
+ struct AutoSaveRestoreRenderingState {
+ explicit AutoSaveRestoreRenderingState(PresShell* aPresShell)
+ : mPresShell(aPresShell)
+ , mOldState(aPresShell)
+ {}
+
+ ~AutoSaveRestoreRenderingState()
+ {
+ mPresShell->mRenderFlags = mOldState.mRenderFlags;
+ mPresShell->mResolution = mOldState.mResolution;
+ }
+
+ PresShell* mPresShell;
+ RenderingState mOldState;
+ };
+ static RenderFlags ChangeFlag(RenderFlags aFlags, bool aOnOff,
+ eRenderFlag aFlag)
+ {
+ return aOnOff ? (aFlags | aFlag) : (aFlag & ~aFlag);
+ }
+
+
+ void SetRenderingState(const RenderingState& aState);
+
+ friend class nsPresShellEventCB;
+
+ bool mCaretEnabled;
+
+#ifdef DEBUG
+ nsStyleSet* CloneStyleSet(nsStyleSet* aSet);
+ bool VerifyIncrementalReflow();
+ bool mInVerifyReflow;
+ void ShowEventTargetDebug();
+#endif
+
+ void RecordStyleSheetChange(mozilla::StyleSheet* aStyleSheet);
+
+ void RemovePreferenceStyles();
+
+ // methods for painting a range to an offscreen buffer
+
+ // given a display list, clip the items within the list to
+ // the range
+ nsRect ClipListToRange(nsDisplayListBuilder *aBuilder,
+ nsDisplayList* aList,
+ nsRange* aRange);
+
+ // create a RangePaintInfo for the range aRange containing the
+ // display list needed to paint the range to a surface
+ mozilla::UniquePtr<RangePaintInfo>
+ CreateRangePaintInfo(nsIDOMRange* aRange,
+ nsRect& aSurfaceRect,
+ bool aForPrimarySelection);
+
+ /*
+ * Paint the items to a new surface and return it.
+ *
+ * aSelection - selection being painted, if any
+ * aRegion - clip region, if any
+ * aArea - area that the surface occupies, relative to the root frame
+ * aPoint - reference point, typically the mouse position
+ * aScreenRect - [out] set to the area of the screen the painted area should
+ * be displayed at
+ * aFlags - set RENDER_AUTO_SCALE to scale down large images, but it must not
+ * be set if a custom image was specified
+ */
+ already_AddRefed<SourceSurface>
+ PaintRangePaintInfo(const nsTArray<mozilla::UniquePtr<RangePaintInfo>>& aItems,
+ nsISelection* aSelection,
+ nsIntRegion* aRegion,
+ nsRect aArea,
+ const mozilla::LayoutDeviceIntPoint aPoint,
+ mozilla::LayoutDeviceIntRect* aScreenRect,
+ uint32_t aFlags);
+
+ /**
+ * Methods to handle changes to user and UA sheet lists that we get
+ * notified about.
+ */
+ void AddUserSheet(nsISupports* aSheet);
+ void AddAgentSheet(nsISupports* aSheet);
+ void AddAuthorSheet(nsISupports* aSheet);
+ void RemoveSheet(mozilla::SheetType aType, nsISupports* aSheet);
+
+ // Hide a view if it is a popup
+ void HideViewIfPopup(nsView* aView);
+
+ // Utility method to restore the root scrollframe state
+ void RestoreRootScrollPosition();
+
+ void MaybeReleaseCapturingContent();
+
+ nsresult HandleRetargetedEvent(mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ nsIContent* aTarget)
+ {
+ PushCurrentEventInfo(nullptr, nullptr);
+ mCurrentEventContent = aTarget;
+ nsresult rv = NS_OK;
+ if (GetCurrentEventFrame()) {
+ rv = HandleEventInternal(aEvent, aStatus, true);
+ }
+ PopCurrentEventInfo();
+ return rv;
+ }
+
+ class DelayedEvent
+ {
+ public:
+ virtual ~DelayedEvent() { }
+ virtual void Dispatch() { }
+ };
+
+ class DelayedInputEvent : public DelayedEvent
+ {
+ public:
+ virtual void Dispatch() override;
+
+ protected:
+ DelayedInputEvent();
+ virtual ~DelayedInputEvent();
+
+ mozilla::WidgetInputEvent* mEvent;
+ };
+
+ class DelayedMouseEvent : public DelayedInputEvent
+ {
+ public:
+ explicit DelayedMouseEvent(mozilla::WidgetMouseEvent* aEvent);
+ };
+
+ class DelayedKeyEvent : public DelayedInputEvent
+ {
+ public:
+ explicit DelayedKeyEvent(mozilla::WidgetKeyboardEvent* aEvent);
+ };
+
+ // Check if aEvent is a mouse event and record the mouse location for later
+ // synth mouse moves.
+ void RecordMouseLocation(mozilla::WidgetGUIEvent* aEvent);
+ class nsSynthMouseMoveEvent final : public nsARefreshObserver {
+ public:
+ nsSynthMouseMoveEvent(PresShell* aPresShell, bool aFromScroll)
+ : mPresShell(aPresShell), mFromScroll(aFromScroll) {
+ NS_ASSERTION(mPresShell, "null parameter");
+ }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsSynthMouseMoveEvent() {
+ Revoke();
+ }
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsSynthMouseMoveEvent, override)
+
+ void Revoke() {
+ if (mPresShell) {
+ mPresShell->GetPresContext()->RefreshDriver()->
+ RemoveRefreshObserver(this, Flush_Display);
+ mPresShell = nullptr;
+ }
+ }
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override {
+ if (mPresShell) {
+ RefPtr<PresShell> shell = mPresShell;
+ shell->ProcessSynthMouseMoveEvent(mFromScroll);
+ }
+ }
+ private:
+ PresShell* mPresShell;
+ bool mFromScroll;
+ };
+ void ProcessSynthMouseMoveEvent(bool aFromScroll);
+
+ void QueryIsActive();
+ nsresult UpdateImageLockingState();
+
+ bool InZombieDocument(nsIContent *aContent);
+ already_AddRefed<nsIPresShell> GetParentPresShellForEventHandling();
+ nsIContent* GetCurrentEventContent();
+ nsIFrame* GetCurrentEventFrame();
+ nsresult RetargetEventToParent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+ void PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent);
+ void PopCurrentEventInfo();
+ /**
+ * @param aIsHandlingNativeEvent true when the caller (perhaps) handles
+ * an event which is caused by native
+ * event. Otherwise, false.
+ */
+ nsresult HandleEventInternal(mozilla::WidgetEvent* aEvent,
+ nsEventStatus* aStatus,
+ bool aIsHandlingNativeEvent);
+ nsresult HandlePositionedEvent(nsIFrame* aTargetFrame,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+ // This returns the focused DOM window under our top level window.
+ // I.e., when we are deactive, this returns the *last* focused DOM window.
+ already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow();
+
+ /*
+ * This and the next two helper methods are used to target and position the
+ * context menu when the keyboard shortcut is used to open it.
+ *
+ * If another menu is open, the context menu is opened relative to the
+ * active menuitem within the menu, or the menu itself if no item is active.
+ * Otherwise, if the caret is visible, the menu is opened near the caret.
+ * Otherwise, if a selectable list such as a listbox is focused, the
+ * current item within the menu is opened relative to this item.
+ * Otherwise, the context menu is opened at the topleft corner of the
+ * view.
+ *
+ * Returns true if the context menu event should fire and false if it should
+ * not.
+ */
+ bool AdjustContextMenuKeyEvent(mozilla::WidgetMouseEvent* aEvent);
+
+ //
+ bool PrepareToUseCaretPosition(nsIWidget* aEventWidget,
+ mozilla::LayoutDeviceIntPoint& aTargetPt);
+
+ // Get the selected item and coordinates in device pixels relative to root
+ // document's root view for element, first ensuring the element is onscreen
+ void GetCurrentItemAndPositionForElement(nsIDOMElement *aCurrentEl,
+ nsIContent **aTargetToUse,
+ mozilla::LayoutDeviceIntPoint& aTargetPt,
+ nsIWidget *aRootWidget);
+
+ void FireResizeEvent();
+ static void AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell);
+
+ virtual void SynthesizeMouseMove(bool aFromScroll) override;
+
+ PresShell* GetRootPresShell();
+
+ nscolor GetDefaultBackgroundColorToDraw();
+
+ DOMHighResTimeStamp GetPerformanceNow();
+
+ // The callback for the mPaintSuppressionTimer timer.
+ static void sPaintSuppressionCallback(nsITimer* aTimer, void* aPresShell);
+
+ // The callback for the mReflowContinueTimer timer.
+ static void sReflowContinueCallback(nsITimer* aTimer, void* aPresShell);
+ bool ScheduleReflowOffTimer();
+
+ // Widget notificiations
+ virtual void WindowSizeMoveDone() override;
+ virtual void SysColorChanged() override { mPresContext->SysColorChanged(); }
+ virtual void ThemeChanged() override { mPresContext->ThemeChanged(); }
+ virtual void BackingScaleFactorChanged() override { mPresContext->UIResolutionChanged(); }
+#ifdef ANDROID
+ virtual nsIDocument* GetTouchEventTargetDocument() override;
+#endif
+
+ virtual void PausePainting() override;
+ virtual void ResumePainting() override;
+
+ void UpdateActivePointerState(mozilla::WidgetGUIEvent* aEvent);
+
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Approximate frame visibility tracking implementation.
+ //////////////////////////////////////////////////////////////////////////////
+
+ void UpdateApproximateFrameVisibility();
+ void DoUpdateApproximateFrameVisibility(bool aRemoveOnly);
+
+ void ClearApproximatelyVisibleFramesList(Maybe<mozilla::OnNonvisible> aNonvisibleAction
+ = Nothing());
+ static void ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear);
+ static void MarkFramesInListApproximatelyVisible(const nsDisplayList& aList,
+ Maybe<VisibleRegions>& aVisibleRegions);
+ void MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame,
+ const nsRect& aRect,
+ Maybe<VisibleRegions>& aVisibleRegions,
+ bool aRemoveOnly = false);
+
+ void DecApproximateVisibleCount(VisibleFrames& aFrames,
+ Maybe<OnNonvisible> aNonvisibleAction = Nothing());
+
+ nsRevocableEventPtr<nsRunnableMethod<PresShell>> mUpdateApproximateFrameVisibilityEvent;
+
+ // A set of frames that were visible or could be visible soon at the time
+ // that we last did an approximate frame visibility update.
+ VisibleFrames mApproximatelyVisibleFrames;
+
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Methods for dispatching KeyboardEvent and BeforeAfterKeyboardEvent.
+ //////////////////////////////////////////////////////////////////////////////
+
+ void HandleKeyboardEvent(nsINode* aTarget,
+ mozilla::WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled,
+ nsEventStatus* aStatus,
+ mozilla::EventDispatchingCallback* aEventCB);
+ void DispatchBeforeKeyboardEventInternal(
+ const nsTArray<nsCOMPtr<mozilla::dom::Element> >& aChain,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ size_t& aChainIndex,
+ bool& aDefaultPrevented);
+ void DispatchAfterKeyboardEventInternal(
+ const nsTArray<nsCOMPtr<mozilla::dom::Element> >& aChain,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ bool aEmbeddedCancelled,
+ size_t aChainIndex = 0);
+
+#ifdef MOZ_B2G
+ // This method is used to forward the keyboard event to the input-method-app
+ // before the event is dispatched to its event target.
+ // Return true if it's successfully forwarded. Otherwise, return false.
+ bool ForwardKeyToInputMethodApp(nsINode* aTarget,
+ mozilla::WidgetKeyboardEvent& aEvent,
+ nsEventStatus* aStatus);
+#endif // MOZ_B2G
+
+ // This method tries forwarding key events to the input-method-editor(IME).
+ // If the event isn't be forwarded, then it will be dispathed to its target.
+ // Return true when event is successfully forwarded to the input-method-editor.
+ // Otherwise, return false.
+ bool ForwardKeyToInputMethodAppOrDispatch(bool aIsTargetRemote,
+ nsINode* aTarget,
+ mozilla::WidgetKeyboardEvent& aEvent,
+ nsEventStatus* aStatus,
+ mozilla::EventDispatchingCallback* aEventCB);
+
+ nsresult SetResolutionImpl(float aResolution, bool aScaleToResolution);
+
+#ifdef DEBUG
+ // The reflow root under which we're currently reflowing. Null when
+ // not in reflow.
+ nsIFrame* mCurrentReflowRoot;
+ uint32_t mUpdateCount;
+#endif
+
+#ifdef MOZ_REFLOW_PERF
+ ReflowCountMgr* mReflowCountMgr;
+#endif
+
+ // This is used for synthetic mouse events that are sent when what is under
+ // the mouse pointer may have changed without the mouse moving (eg scrolling,
+ // change to the document contents).
+ // It is set only on a presshell for a root document, this value represents
+ // the last observed location of the mouse relative to that root document. It
+ // is set to (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if the mouse isn't
+ // over our window or there is no last observed mouse location for some
+ // reason.
+ nsPoint mMouseLocation;
+ // This is an APZ state variable that tracks the target guid for the last
+ // mouse event that was processed (corresponding to mMouseLocation). This is
+ // needed for the synthetic mouse events.
+ mozilla::layers::ScrollableLayerGuid mMouseEventTargetGuid;
+
+ // mStyleSet owns it but we maintain a ref, may be null
+ RefPtr<mozilla::StyleSheet> mPrefStyleSheet;
+
+ // Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
+ // we finish reflowing mCurrentReflowRoot.
+ nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesToDirty;
+
+ // Reflow roots that need to be reflowed.
+ nsTArray<nsIFrame*> mDirtyRoots;
+
+ nsTArray<nsAutoPtr<DelayedEvent> > mDelayedEvents;
+ nsRevocableEventPtr<nsRunnableMethod<PresShell> > mResizeEvent;
+ nsCOMPtr<nsITimer> mAsyncResizeEventTimer;
+private:
+ nsIFrame* mCurrentEventFrame;
+ nsCOMPtr<nsIContent> mCurrentEventContent;
+ nsTArray<nsIFrame*> mCurrentEventFrameStack;
+ nsCOMArray<nsIContent> mCurrentEventContentStack;
+protected:
+ nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
+ nsCOMPtr<nsIContent> mLastAnchorScrolledTo;
+ RefPtr<nsCaret> mCaret;
+ RefPtr<nsCaret> mOriginalCaret;
+ nsCallbackEventRequest* mFirstCallbackEventRequest;
+ nsCallbackEventRequest* mLastCallbackEventRequest;
+
+ mozilla::TouchManager mTouchManager;
+
+ RefPtr<ZoomConstraintsClient> mZoomConstraintsClient;
+ RefPtr<MobileViewportManager> mMobileViewportManager;
+
+ RefPtr<mozilla::AccessibleCaretEventHub> mAccessibleCaretEventHub;
+
+ // This timer controls painting suppression. Until it fires
+ // or all frames are constructed, we won't paint anything but
+ // our <body> background and scrollbars.
+ nsCOMPtr<nsITimer> mPaintSuppressionTimer;
+
+ nsCOMPtr<nsITimer> mDelayedPaintTimer;
+
+ // The `performance.now()` value when we last started to process reflows.
+ DOMHighResTimeStamp mLastReflowStart;
+
+ mozilla::TimeStamp mLoadBegin; // used to time loads
+
+ // Information needed to properly handle scrolling content into view if the
+ // pre-scroll reflow flush can be interrupted. mContentToScrollTo is
+ // non-null between the initial scroll attempt and the first time we finish
+ // processing all our dirty roots. mContentToScrollTo has a content property
+ // storing the details for the scroll operation, see ScrollIntoViewData above.
+ nsCOMPtr<nsIContent> mContentToScrollTo;
+
+ nscoord mLastAnchorScrollPositionY;
+
+ // Information about live content (which still stay in DOM tree).
+ // Used in case we need re-dispatch event after sending pointer event,
+ // when target of pointer event was deleted during executing user handlers.
+ nsCOMPtr<nsIContent> mPointerEventTarget;
+
+ // This is used to protect ourselves from triggering reflow while in the
+ // middle of frame construction and the like... it really shouldn't be
+ // needed, one hopes, but it is for now.
+ uint16_t mChangeNestCount;
+
+ bool mDocumentLoading : 1;
+ bool mIgnoreFrameDestruction : 1;
+ bool mHaveShutDown : 1;
+ bool mLastRootReflowHadUnconstrainedBSize : 1;
+ bool mNoDelayedMouseEvents : 1;
+ bool mNoDelayedKeyEvents : 1;
+
+ // We've been disconnected from the document. We will refuse to paint the
+ // document until either our timer fires or all frames are constructed.
+ bool mIsDocumentGone : 1;
+
+ // Indicates that it is safe to unlock painting once all pending reflows
+ // have been processed.
+ bool mShouldUnsuppressPainting : 1;
+
+ bool mAsyncResizeTimerIsActive : 1;
+ bool mInResize : 1;
+
+ bool mApproximateFrameVisibilityVisited : 1;
+
+ bool mNextPaintCompressed : 1;
+
+ bool mHasCSSBackgroundColor : 1;
+
+ // Whether content should be scaled by the resolution amount. If this is
+ // not set, a transform that scales by the inverse of the resolution is
+ // applied to rendered layers.
+ bool mScaleToResolution : 1;
+
+ // Whether the last chrome-only escape key event is consumed.
+ bool mIsLastChromeOnlyEscapeKeyConsumed : 1;
+
+ // Whether the widget has received a paint message yet.
+ bool mHasReceivedPaintMessage : 1;
+
+ static bool sDisableNonTestMouseEvents;
+};
+
+#endif /* !defined(nsPresShell_h_) */
diff --git a/layout/base/nsPresState.h b/layout/base/nsPresState.h
new file mode 100644
index 000000000..ced3eae76
--- /dev/null
+++ b/layout/base/nsPresState.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a piece of state that is stored in session history when the document
+ * is not
+ */
+
+#ifndef nsPresState_h_
+#define nsPresState_h_
+
+#include "nsPoint.h"
+#include "gfxPoint.h"
+#include "nsAutoPtr.h"
+
+class nsPresState
+{
+public:
+ nsPresState()
+ : mContentData(nullptr)
+ , mScrollState(0, 0)
+ , mAllowScrollOriginDowngrade(true)
+ , mResolution(1.0)
+ , mScaleToResolution(false)
+ , mDisabledSet(false)
+ , mDisabled(false)
+ , mDroppedDown(false)
+ {}
+
+ void SetScrollState(const nsPoint& aState)
+ {
+ mScrollState = aState;
+ }
+
+ nsPoint GetScrollPosition() const
+ {
+ return mScrollState;
+ }
+
+ void SetAllowScrollOriginDowngrade(bool aAllowScrollOriginDowngrade)
+ {
+ mAllowScrollOriginDowngrade = aAllowScrollOriginDowngrade;
+ }
+
+ bool GetAllowScrollOriginDowngrade()
+ {
+ return mAllowScrollOriginDowngrade;
+ }
+
+ void SetResolution(float aSize)
+ {
+ mResolution = aSize;
+ }
+
+ float GetResolution() const
+ {
+ return mResolution;
+ }
+
+ void SetScaleToResolution(bool aScaleToResolution)
+ {
+ mScaleToResolution = aScaleToResolution;
+ }
+
+ bool GetScaleToResolution() const
+ {
+ return mScaleToResolution;
+ }
+
+ void ClearNonScrollState()
+ {
+ mContentData = nullptr;
+ mDisabledSet = false;
+ }
+
+ bool GetDisabled() const
+ {
+ return mDisabled;
+ }
+
+ void SetDisabled(bool aDisabled)
+ {
+ mDisabled = aDisabled;
+ mDisabledSet = true;
+ }
+
+ bool IsDisabledSet() const
+ {
+ return mDisabledSet;
+ }
+
+ nsISupports* GetStateProperty() const
+ {
+ return mContentData;
+ }
+
+ void SetStateProperty(nsISupports *aProperty)
+ {
+ mContentData = aProperty;
+ }
+
+ void SetDroppedDown(bool aDroppedDown)
+ {
+ mDroppedDown = aDroppedDown;
+ }
+
+ bool GetDroppedDown() const
+ {
+ return mDroppedDown;
+ }
+
+// MEMBER VARIABLES
+protected:
+ nsCOMPtr<nsISupports> mContentData;
+ nsPoint mScrollState;
+ bool mAllowScrollOriginDowngrade;
+ float mResolution;
+ bool mScaleToResolution;
+ bool mDisabledSet;
+ bool mDisabled;
+ bool mDroppedDown;
+};
+
+#endif /* nsPresState_h_ */
diff --git a/layout/base/nsQuoteList.cpp b/layout/base/nsQuoteList.cpp
new file mode 100644
index 000000000..9f742f06a
--- /dev/null
+++ b/layout/base/nsQuoteList.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of quotes for the CSS 'content' property */
+
+#include "nsQuoteList.h"
+#include "nsReadableUtils.h"
+#include "nsIContent.h"
+
+bool
+nsQuoteNode::InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame)
+{
+ nsGenConNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
+
+ nsQuoteList* quoteList = static_cast<nsQuoteList*>(aList);
+ bool dirty = false;
+ quoteList->Insert(this);
+ if (quoteList->IsLast(this))
+ quoteList->Calc(this);
+ else
+ dirty = true;
+
+ // Don't set up text for 'no-open-quote' and 'no-close-quote'.
+ if (IsRealQuote()) {
+ aTextFrame->GetContent()->SetText(*Text(), false);
+ }
+ return dirty;
+}
+
+const nsString*
+nsQuoteNode::Text()
+{
+ NS_ASSERTION(mType == eStyleContentType_OpenQuote ||
+ mType == eStyleContentType_CloseQuote,
+ "should only be called when mText should be non-null");
+ const nsStyleQuoteValues::QuotePairArray& quotePairs =
+ mPseudoFrame->StyleList()->GetQuotePairs();
+ int32_t quotesCount = quotePairs.Length(); // 0 if 'quotes:none'
+ int32_t quoteDepth = Depth();
+
+ // Reuse the last pair when the depth is greater than the number of
+ // pairs of quotes. (Also make 'quotes: none' and close-quote from
+ // a depth of 0 equivalent for the next test.)
+ if (quoteDepth >= quotesCount)
+ quoteDepth = quotesCount - 1;
+
+ const nsString* result;
+ if (quoteDepth == -1) {
+ // close-quote from a depth of 0 or 'quotes: none' (we want a node
+ // with the empty string so dynamic changes are easier to handle)
+ result = &EmptyString();
+ } else {
+ result = eStyleContentType_OpenQuote == mType
+ ? &quotePairs[quoteDepth].first
+ : &quotePairs[quoteDepth].second;
+ }
+ return result;
+}
+
+void
+nsQuoteList::Calc(nsQuoteNode* aNode)
+{
+ if (aNode == FirstNode()) {
+ aNode->mDepthBefore = 0;
+ } else {
+ aNode->mDepthBefore = Prev(aNode)->DepthAfter();
+ }
+}
+
+void
+nsQuoteList::RecalcAll()
+{
+ for (nsQuoteNode* node = FirstNode(); node; node = Next(node)) {
+ int32_t oldDepth = node->mDepthBefore;
+ Calc(node);
+
+ if (node->mDepthBefore != oldDepth && node->mText && node->IsRealQuote())
+ node->mText->SetData(*node->Text());
+ }
+}
+
+#ifdef DEBUG
+void
+nsQuoteList::PrintChain()
+{
+ printf("Chain: \n");
+ for (nsQuoteNode* node = FirstNode(); node; node = Next(node)) {
+ printf(" %p %d - ", static_cast<void*>(node), node->mDepthBefore);
+ switch(node->mType) {
+ case (eStyleContentType_OpenQuote):
+ printf("open");
+ break;
+ case (eStyleContentType_NoOpenQuote):
+ printf("noOpen");
+ break;
+ case (eStyleContentType_CloseQuote):
+ printf("close");
+ break;
+ case (eStyleContentType_NoCloseQuote):
+ printf("noClose");
+ break;
+ default:
+ printf("unknown!!!");
+ }
+ printf(" %d - %d,", node->Depth(), node->DepthAfter());
+ if (node->mText) {
+ nsAutoString data;
+ node->mText->GetData(data);
+ printf(" \"%s\",", NS_ConvertUTF16toUTF8(data).get());
+ }
+ printf("\n");
+ }
+}
+#endif
diff --git a/layout/base/nsQuoteList.h b/layout/base/nsQuoteList.h
new file mode 100644
index 000000000..4f0a2e3de
--- /dev/null
+++ b/layout/base/nsQuoteList.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of quotes for the CSS 'content' property */
+
+#ifndef nsQuoteList_h___
+#define nsQuoteList_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenConList.h"
+
+struct nsQuoteNode : public nsGenConNode {
+ // open-quote, close-quote, no-open-quote, or no-close-quote
+ const nsStyleContentType mType;
+
+ // Quote depth before this quote, which is always non-negative.
+ int32_t mDepthBefore;
+
+ nsQuoteNode(nsStyleContentType& aType, uint32_t aContentIndex)
+ : nsGenConNode(aContentIndex)
+ , mType(aType)
+ , mDepthBefore(0)
+ {
+ NS_ASSERTION(aType == eStyleContentType_OpenQuote ||
+ aType == eStyleContentType_CloseQuote ||
+ aType == eStyleContentType_NoOpenQuote ||
+ aType == eStyleContentType_NoCloseQuote,
+ "incorrect type");
+ NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
+ }
+
+ virtual bool InitTextFrame(nsGenConList* aList,
+ nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) override;
+
+ // is this 'open-quote' or 'no-open-quote'?
+ bool IsOpenQuote() {
+ return mType == eStyleContentType_OpenQuote ||
+ mType == eStyleContentType_NoOpenQuote;
+ }
+
+ // is this 'close-quote' or 'no-close-quote'?
+ bool IsCloseQuote() {
+ return !IsOpenQuote();
+ }
+
+ // is this 'open-quote' or 'close-quote'?
+ bool IsRealQuote() {
+ return mType == eStyleContentType_OpenQuote ||
+ mType == eStyleContentType_CloseQuote;
+ }
+
+ // Depth of the quote for *this* node. Either non-negative or -1.
+ // -1 means this is a closing quote that tried to decrement the
+ // counter below zero (which means no quote should be rendered).
+ int32_t Depth() {
+ return IsOpenQuote() ? mDepthBefore : mDepthBefore - 1;
+ }
+
+ // always non-negative
+ int32_t DepthAfter() {
+ return IsOpenQuote() ? mDepthBefore + 1
+ : (mDepthBefore == 0 ? 0 : mDepthBefore - 1);
+ }
+
+ // The text that should be displayed for this quote.
+ const nsString* Text();
+};
+
+class nsQuoteList : public nsGenConList {
+private:
+ nsQuoteNode* FirstNode() { return static_cast<nsQuoteNode*>(mList.getFirst()); }
+public:
+ // assign the correct |mDepthBefore| value to a node that has been inserted
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsQuoteNode* aNode);
+
+ nsQuoteNode* Next(nsQuoteNode* aNode) {
+ return static_cast<nsQuoteNode*>(nsGenConList::Next(aNode));
+ }
+ nsQuoteNode* Prev(nsQuoteNode* aNode) {
+ return static_cast<nsQuoteNode*>(nsGenConList::Prev(aNode));
+ }
+
+ void RecalcAll();
+#ifdef DEBUG
+ void PrintChain();
+#endif
+};
+
+#endif /* nsQuoteList_h___ */
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
new file mode 100644
index 000000000..6676bea97
--- /dev/null
+++ b/layout/base/nsRefreshDriver.cpp
@@ -0,0 +1,2373 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Code to notify things that animate before a refresh, at an appropriate
+ * refresh rate. (Perhaps temporary, until replaced by compositor.)
+ *
+ * Chrome and each tab have their own RefreshDriver, which in turn
+ * hooks into one of a few global timer based on RefreshDriverTimer,
+ * defined below. There are two main global timers -- one for active
+ * animations, and one for inactive ones. These are implemented as
+ * subclasses of RefreshDriverTimer; see below for a description of
+ * their implementations. In the future, additional timer types may
+ * implement things like blocking on vsync.
+ */
+
+#ifdef XP_WIN
+#include <windows.h>
+// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
+// to manually include it
+#include <mmsystem.h>
+#include "WinUtils.h"
+#endif
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/IntegerRange.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsRefreshDriver.h"
+#include "nsITimer.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "nsAutoPtr.h"
+#include "nsIDocument.h"
+#include "jsapi.h"
+#include "nsContentUtils.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/Preferences.h"
+#include "nsViewManager.h"
+#include "GeckoProfiler.h"
+#include "nsNPAPIPluginInstance.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "Layers.h"
+#include "imgIContainer.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsDocShell.h"
+#include "nsISimpleEnumerator.h"
+#include "nsJSEnvironment.h"
+#include "mozilla/Telemetry.h"
+#include "gfxPrefs.h"
+#include "BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "mozilla/layout/VsyncChild.h"
+#include "VsyncSource.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/TimelineConsumers.h"
+#include "nsAnimationManager.h"
+#include "nsIDOMEvent.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::ipc;
+using namespace mozilla::layout;
+
+static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
+#define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+#define DEFAULT_THROTTLED_FRAME_RATE 1
+#define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000
+#define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100
+// after 10 minutes, stop firing off inactive timers
+#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
+
+// The number of seconds spent skipping frames because we are waiting for the compositor
+// before logging.
+#if defined(MOZ_ASAN)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && !defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)
+#elif defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)
+#else
+# define REFRESH_WAIT_WARNING 1
+#endif
+
+namespace {
+ // `true` if we are currently in jank-critical mode.
+ //
+ // In jank-critical mode, any iteration of the event loop that takes
+ // more than 16ms to compute will cause an ongoing animation to miss
+ // frames.
+ //
+ // For simplicity, the current implementation assumes that we are in
+ // jank-critical mode if and only if at least one vsync driver has
+ // at least one observer.
+ static uint64_t sActiveVsyncTimers = 0;
+
+ // The latest value of process-wide jank levels.
+ //
+ // For each i, sJankLevels[i] counts the number of times delivery of
+ // vsync to the main thread has been delayed by at least 2^i ms. Use
+ // GetJankLevels to grab a copy of this array.
+ uint64_t sJankLevels[12];
+
+ // The number outstanding nsRefreshDrivers (that have been created but not
+ // disconnected). When this reaches zero we will call
+ // nsRefreshDriver::Shutdown.
+ static uint32_t sRefreshDriverCount = 0;
+}
+
+namespace mozilla {
+
+/*
+ * The base class for all global refresh driver timers. It takes care
+ * of managing the list of refresh drivers attached to them and
+ * provides interfaces for querying/setting the rate and actually
+ * running a timer 'Tick'. Subclasses must implement StartTimer(),
+ * StopTimer(), and ScheduleNextTick() -- the first two just
+ * start/stop whatever timer mechanism is in use, and ScheduleNextTick
+ * is called at the start of the Tick() implementation to set a time
+ * for the next tick.
+ */
+class RefreshDriverTimer {
+public:
+ RefreshDriverTimer()
+ : mLastFireEpoch(0)
+ , mLastFireSkipped(false)
+ {
+ }
+
+ virtual ~RefreshDriverTimer()
+ {
+ MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!");
+ MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!");
+ }
+
+ virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ LOG("[%p] AddRefreshDriver %p", this, aDriver);
+
+ bool startTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver), "Adding a duplicate root refresh driver!");
+ mRootRefreshDrivers.AppendElement(aDriver);
+ } else {
+ NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver), "Adding a duplicate content refresh driver!");
+ mContentRefreshDrivers.AppendElement(aDriver);
+ }
+
+ if (startTimer) {
+ StartTimer();
+ }
+ }
+
+ virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
+
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the root refresh list!");
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ // During PresContext shutdown, we can't accurately detect
+ // if a root refresh driver exists or not. Therefore, we have to
+ // search and find out which list this driver exists in.
+ if (!rootContext) {
+ if (mRootRefreshDrivers.Contains(aDriver)) {
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
+ "RemoveRefreshDriver without a display root for a driver that is not in the content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a driver that is not in the content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ }
+
+ bool stopTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (stopTimer) {
+ StopTimer();
+ }
+ }
+
+ TimeStamp MostRecentRefresh() const { return mLastFireTime; }
+ int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; }
+
+ void SwapRefreshDrivers(RefreshDriverTimer* aNewTimer)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (nsRefreshDriver* driver : mContentRefreshDrivers) {
+ aNewTimer->AddRefreshDriver(driver);
+ driver->mActiveTimer = aNewTimer;
+ }
+ mContentRefreshDrivers.Clear();
+
+ for (nsRefreshDriver* driver : mRootRefreshDrivers) {
+ aNewTimer->AddRefreshDriver(driver);
+ driver->mActiveTimer = aNewTimer;
+ }
+ mRootRefreshDrivers.Clear();
+
+ aNewTimer->mLastFireEpoch = mLastFireEpoch;
+ aNewTimer->mLastFireTime = mLastFireTime;
+ }
+
+ virtual TimeDuration GetTimerRate() = 0;
+
+ bool LastTickSkippedAnyPaints() const
+ {
+ return mLastFireSkipped;
+ }
+
+ Maybe<TimeStamp> GetIdleDeadlineHint()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (LastTickSkippedAnyPaints()) {
+ return Some(TimeStamp());
+ }
+
+ TimeStamp mostRecentRefresh = MostRecentRefresh();
+ TimeDuration refreshRate = GetTimerRate();
+ TimeStamp idleEnd = mostRecentRefresh + refreshRate;
+
+ if (idleEnd +
+ refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() <
+ TimeStamp::Now()) {
+ return Nothing();
+ }
+
+ return Some(idleEnd - TimeDuration::FromMilliseconds(
+ nsLayoutUtils::IdlePeriodDeadlineLimit()));
+ }
+
+protected:
+ virtual void StartTimer() = 0;
+ virtual void StopTimer() = 0;
+ virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
+
+ bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (!rootContext) {
+ return false;
+ }
+
+ return aDriver == rootContext->RefreshDriver();
+ }
+
+ /*
+ * Actually runs a tick, poking all the attached RefreshDrivers.
+ * Grabs the "now" time via JS_Now and TimeStamp::Now().
+ */
+ void Tick()
+ {
+ int64_t jsnow = JS_Now();
+ TimeStamp now = TimeStamp::Now();
+ Tick(jsnow, now);
+ }
+
+ void TickRefreshDrivers(int64_t aJsNow, TimeStamp aNow, nsTArray<RefPtr<nsRefreshDriver>>& aDrivers)
+ {
+ if (aDrivers.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<RefPtr<nsRefreshDriver> > drivers(aDrivers);
+ for (nsRefreshDriver* driver : drivers) {
+ // don't poke this driver if it's in test mode
+ if (driver->IsTestControllingRefreshesEnabled()) {
+ continue;
+ }
+
+ TickDriver(driver, aJsNow, aNow);
+
+ mLastFireSkipped = mLastFireSkipped || driver->mSkippedPaints;
+ }
+ }
+
+ /*
+ * Tick the refresh drivers based on the given timestamp.
+ */
+ void Tick(int64_t jsnow, TimeStamp now)
+ {
+ ScheduleNextTick(now);
+
+ mLastFireEpoch = jsnow;
+ mLastFireTime = now;
+ mLastFireSkipped = false;
+
+ LOG("[%p] ticking drivers...", this);
+ // RD is short for RefreshDriver
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
+
+ TickRefreshDrivers(jsnow, now, mContentRefreshDrivers);
+ TickRefreshDrivers(jsnow, now, mRootRefreshDrivers);
+
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
+ LOG("[%p] done.", this);
+ }
+
+ static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
+ {
+ LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
+ driver->Tick(jsnow, now);
+ }
+
+ int64_t mLastFireEpoch;
+ bool mLastFireSkipped;
+ TimeStamp mLastFireTime;
+ TimeStamp mTargetTime;
+
+ nsTArray<RefPtr<nsRefreshDriver> > mContentRefreshDrivers;
+ nsTArray<RefPtr<nsRefreshDriver> > mRootRefreshDrivers;
+
+ // useful callback for nsITimer-based derived classes, here
+ // bacause of c++ protected shenanigans
+ static void TimerTick(nsITimer* aTimer, void* aClosure)
+ {
+ RefreshDriverTimer *timer = static_cast<RefreshDriverTimer*>(aClosure);
+ timer->Tick();
+ }
+};
+
+/*
+ * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
+ * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
+ * implement ScheduleNextTick and intelligently calculate the next time to tick,
+ * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
+ * with its attempt at intelligent slack removal and such, so we don't do it.
+ */
+class SimpleTimerBasedRefreshDriverTimer :
+ public RefreshDriverTimer
+{
+public:
+ /*
+ * aRate -- the delay, in milliseconds, requested between timer firings
+ */
+ explicit SimpleTimerBasedRefreshDriverTimer(double aRate)
+ {
+ SetRate(aRate);
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ ~SimpleTimerBasedRefreshDriverTimer() override
+ {
+ StopTimer();
+ }
+
+ // will take effect at next timer tick
+ virtual void SetRate(double aNewRate)
+ {
+ mRateMilliseconds = aNewRate;
+ mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
+ }
+
+ double GetRate() const
+ {
+ return mRateMilliseconds;
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ return mRateDuration;
+ }
+
+protected:
+
+ void StartTimer() override
+ {
+ // pretend we just fired, and we schedule the next tick normally
+ mLastFireEpoch = JS_Now();
+ mLastFireTime = TimeStamp::Now();
+
+ mTargetTime = mLastFireTime + mRateDuration;
+
+ uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
+ mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ void StopTimer() override
+ {
+ mTimer->Cancel();
+ }
+
+ double mRateMilliseconds;
+ TimeDuration mRateDuration;
+ RefPtr<nsITimer> mTimer;
+};
+
+/*
+ * A refresh driver that listens to vsync events and ticks the refresh driver
+ * on vsync intervals. We throttle the refresh driver if we get too many
+ * vsync events and wait to catch up again.
+ */
+class VsyncRefreshDriverTimer : public RefreshDriverTimer
+{
+public:
+ VsyncRefreshDriverTimer()
+ : mVsyncChild(nullptr)
+ {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncObserver = new RefreshDriverVsyncObserver(this);
+ RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
+ MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher());
+ mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+ mVsyncRate = vsyncSource->GetGlobalDisplay().GetVsyncRate();
+ }
+
+ explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild)
+ : mVsyncChild(aVsyncChild)
+ {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mVsyncChild);
+ mVsyncObserver = new RefreshDriverVsyncObserver(this);
+ mVsyncChild->SetVsyncObserver(mVsyncObserver);
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ if (mVsyncRate != TimeDuration::Forever()) {
+ return mVsyncRate;
+ }
+
+ if (mVsyncChild) {
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return mVsyncRate != TimeDuration::Forever()
+ ? mVsyncRate
+ : TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ }
+
+private:
+ // Since VsyncObservers are refCounted, but the RefreshDriverTimer are
+ // explicitly shutdown. We create an inner class that has the VsyncObserver
+ // and is shutdown when the RefreshDriverTimer is deleted. The alternative is
+ // to (a) make all RefreshDriverTimer RefCounted or (b) use different
+ // VsyncObserver types.
+ class RefreshDriverVsyncObserver final : public VsyncObserver
+ {
+ public:
+ explicit RefreshDriverVsyncObserver(VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
+ : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer)
+ , mRefreshTickLock("RefreshTickLock")
+ , mRecentVsync(TimeStamp::Now())
+ , mLastChildTick(TimeStamp::Now())
+ , mVsyncRate(TimeDuration::Forever())
+ , mProcessedVsync(true)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool NotifyVsync(TimeStamp aVsyncTimestamp) override
+ {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Compress vsync notifications such that only 1 may run at a time
+ // This is so that we don't flood the refresh driver with vsync messages
+ // if the main thread is blocked for long periods of time
+ { // scope lock
+ MonitorAutoLock lock(mRefreshTickLock);
+ mRecentVsync = aVsyncTimestamp;
+ if (!mProcessedVsync) {
+ return true;
+ }
+ mProcessedVsync = false;
+ }
+
+ nsCOMPtr<nsIRunnable> vsyncEvent =
+ NewRunnableMethod<TimeStamp>(this,
+ &RefreshDriverVsyncObserver::TickRefreshDriver,
+ aVsyncTimestamp);
+ NS_DispatchToMainThread(vsyncEvent);
+ } else {
+ TickRefreshDriver(aVsyncTimestamp);
+ }
+
+ return true;
+ }
+
+ void Shutdown()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncRefreshDriverTimer = nullptr;
+ }
+
+ void OnTimerStart()
+ {
+ if (!XRE_IsParentProcess()) {
+ mLastChildTick = TimeStamp::Now();
+ }
+ }
+ private:
+ ~RefreshDriverVsyncObserver() = default;
+
+ void RecordTelemetryProbes(TimeStamp aVsyncTimestamp)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ #ifndef ANDROID /* bug 1142079 */
+ if (XRE_IsParentProcess()) {
+ TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
+ uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
+ sample);
+ RecordJank(sample);
+ } else if (mVsyncRate != TimeDuration::Forever()) {
+ TimeDuration contentDelay = (TimeStamp::Now() - mLastChildTick) - mVsyncRate;
+ if (contentDelay.ToMilliseconds() < 0 ){
+ // Vsyncs are noisy and some can come at a rate quicker than
+ // the reported hardware rate. In those cases, consider that we have 0 delay.
+ contentDelay = TimeDuration::FromMilliseconds(0);
+ }
+ uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
+ sample);
+ RecordJank(sample);
+ } else {
+ // Request the vsync rate from the parent process. Might be a few vsyncs
+ // until the parent responds.
+ mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();
+ }
+ #endif
+ }
+
+ void RecordJank(uint32_t aJankMS)
+ {
+ uint32_t duration = 1 /* ms */;
+ for (size_t i = 0;
+ i < mozilla::ArrayLength(sJankLevels) && duration < aJankMS;
+ ++i, duration *= 2) {
+ sJankLevels[i]++;
+ }
+ }
+
+ void TickRefreshDriver(TimeStamp aVsyncTimestamp)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RecordTelemetryProbes(aVsyncTimestamp);
+ if (XRE_IsParentProcess()) {
+ MonitorAutoLock lock(mRefreshTickLock);
+ aVsyncTimestamp = mRecentVsync;
+ mProcessedVsync = true;
+ } else {
+ mLastChildTick = TimeStamp::Now();
+ }
+ MOZ_ASSERT(aVsyncTimestamp <= TimeStamp::Now());
+
+ // We might have a problem that we call ~VsyncRefreshDriverTimer() before
+ // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
+ // before use.
+ if (mVsyncRefreshDriverTimer) {
+ mVsyncRefreshDriverTimer->RunRefreshDrivers(aVsyncTimestamp);
+ }
+ }
+
+ // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
+ // be always available before Shutdown(). We can just use the raw pointer
+ // here.
+ VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
+ Monitor mRefreshTickLock;
+ TimeStamp mRecentVsync;
+ TimeStamp mLastChildTick;
+ TimeDuration mVsyncRate;
+ bool mProcessedVsync;
+ }; // RefreshDriverVsyncObserver
+
+ ~VsyncRefreshDriverTimer() override
+ {
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(nullptr);
+ mVsyncDispatcher = nullptr;
+ } else {
+ // Since the PVsyncChild actors live through the life of the process, just
+ // send the unobserveVsync message to disable vsync event. We don't need
+ // to handle the cleanup stuff of this actor. PVsyncChild::ActorDestroy()
+ // will be called and clean up this actor.
+ Unused << mVsyncChild->SendUnobserve();
+ mVsyncChild->SetVsyncObserver(nullptr);
+ mVsyncChild = nullptr;
+ }
+
+ // Detach current vsync timer from this VsyncObserver. The observer will no
+ // longer tick this timer.
+ mVsyncObserver->Shutdown();
+ mVsyncObserver = nullptr;
+ }
+
+ void StartTimer() override
+ {
+ // Protect updates to `sActiveVsyncTimers`.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mLastFireEpoch = JS_Now();
+ mLastFireTime = TimeStamp::Now();
+
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+ } else {
+ Unused << mVsyncChild->SendObserve();
+ mVsyncObserver->OnTimerStart();
+ }
+
+ ++sActiveVsyncTimers;
+ }
+
+ void StopTimer() override
+ {
+ // Protect updates to `sActiveVsyncTimers`.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(nullptr);
+ } else {
+ Unused << mVsyncChild->SendUnobserve();
+ }
+
+ MOZ_ASSERT(sActiveVsyncTimers > 0);
+ --sActiveVsyncTimers;
+ }
+
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ // Do nothing since we just wait for the next vsync from
+ // RefreshDriverVsyncObserver.
+ }
+
+ void RunRefreshDrivers(TimeStamp aTimeStamp)
+ {
+ int64_t jsnow = JS_Now();
+ TimeDuration diff = TimeStamp::Now() - aTimeStamp;
+ int64_t vsyncJsNow = jsnow - diff.ToMicroseconds();
+ Tick(vsyncJsNow, aTimeStamp);
+ }
+
+ RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
+ // Used for parent process.
+ RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
+ // Used for child process.
+ // The mVsyncChild will be always available before VsncChild::ActorDestroy().
+ // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
+ RefPtr<VsyncChild> mVsyncChild;
+ TimeDuration mVsyncRate;
+}; // VsyncRefreshDriverTimer
+
+/**
+ * Since the content process takes some time to setup
+ * the vsync IPC connection, this timer is used
+ * during the intial startup process.
+ * During initial startup, the refresh drivers
+ * are ticked off this timer, and are swapped out once content
+ * vsync IPC connection is established.
+ */
+class StartupRefreshDriverTimer :
+ public SimpleTimerBasedRefreshDriverTimer
+{
+public:
+ explicit StartupRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate)
+ {
+ }
+
+protected:
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ // Since this is only used for startup, it isn't super critical
+ // that we tick at consistent intervals.
+ TimeStamp newTarget = aNowTime + mRateDuration;
+ uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
+ mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
+ mTargetTime = newTarget;
+ }
+};
+
+/*
+ * A RefreshDriverTimer for inactive documents. When a new refresh driver is
+ * added, the rate is reset to the base (normally 1s/1fps). Every time
+ * it ticks, a single refresh driver is poked. Once they have all been poked,
+ * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point,
+ * the timer is quiet and doesn't tick (until something is added to it again).
+ *
+ * When a timer is removed, there is a possibility of another timer
+ * being skipped for one cycle. We could avoid this by adjusting
+ * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
+ * add that complexity. All we want is for inactive drivers to tick
+ * at some point, but we don't care too much about how often.
+ */
+class InactiveRefreshDriverTimer final :
+ public SimpleTimerBasedRefreshDriverTimer
+{
+public:
+ explicit InactiveRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(-1.0),
+ mNextDriverIndex(0)
+ {
+ }
+
+ InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(aDisableAfterMilliseconds),
+ mNextDriverIndex(0)
+ {
+ }
+
+ void AddRefreshDriver(nsRefreshDriver* aDriver) override
+ {
+ RefreshDriverTimer::AddRefreshDriver(aDriver);
+
+ LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
+ this, aDriver);
+
+ // reset the timer, and start with the newly added one next time.
+ mNextTickDuration = mRateMilliseconds;
+
+ // we don't really have to start with the newly added one, but we may as well
+ // not tick the old ones at the fastest rate any more than we need to.
+ mNextDriverIndex = GetRefreshDriverCount() - 1;
+
+ StopTimer();
+ StartTimer();
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ return TimeDuration::FromMilliseconds(mNextTickDuration);
+ }
+
+protected:
+ uint32_t GetRefreshDriverCount()
+ {
+ return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
+ }
+
+ void StartTimer() override
+ {
+ mLastFireEpoch = JS_Now();
+ mLastFireTime = TimeStamp::Now();
+
+ mTargetTime = mLastFireTime + mRateDuration;
+
+ uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
+ mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ void StopTimer() override
+ {
+ mTimer->Cancel();
+ }
+
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ if (mDisableAfterMilliseconds > 0.0 &&
+ mNextTickDuration > mDisableAfterMilliseconds)
+ {
+ // We hit the time after which we should disable
+ // inactive window refreshes; don't schedule anything
+ // until we get kicked by an AddRefreshDriver call.
+ return;
+ }
+
+ // double the next tick time if we've already gone through all of them once
+ if (mNextDriverIndex >= GetRefreshDriverCount()) {
+ mNextTickDuration *= 2.0;
+ mNextDriverIndex = 0;
+ }
+
+ // this doesn't need to be precise; do a simple schedule
+ uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
+ mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
+
+ LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration,
+ mNextDriverIndex, GetRefreshDriverCount());
+ }
+
+ /* Runs just one driver's tick. */
+ void TickOne()
+ {
+ int64_t jsnow = JS_Now();
+ TimeStamp now = TimeStamp::Now();
+
+ ScheduleNextTick(now);
+
+ mLastFireEpoch = jsnow;
+ mLastFireTime = now;
+ mLastFireSkipped = false;
+
+ nsTArray<RefPtr<nsRefreshDriver> > drivers(mContentRefreshDrivers);
+ drivers.AppendElements(mRootRefreshDrivers);
+ size_t index = mNextDriverIndex;
+
+ if (index < drivers.Length() &&
+ !drivers[index]->IsTestControllingRefreshesEnabled())
+ {
+ TickDriver(drivers[index], jsnow, now);
+ mLastFireSkipped = mLastFireSkipped || drivers[index]->SkippedPaints();
+ }
+
+ mNextDriverIndex++;
+ }
+
+ static void TimerTickOne(nsITimer* aTimer, void* aClosure)
+ {
+ InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
+ timer->TickOne();
+ }
+
+ double mNextTickDuration;
+ double mDisableAfterMilliseconds;
+ uint32_t mNextDriverIndex;
+};
+
+// The PBackground protocol connection callback. It will be called when
+// PBackground is ready. Then we create the PVsync sub-protocol for our
+// vsync-base RefreshTimer.
+class VsyncChildCreateCallback final : public nsIIPCBackgroundChildCreateCallback
+{
+ NS_DECL_ISUPPORTS
+
+public:
+ VsyncChildCreateCallback()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ static void CreateVsyncActor(PBackgroundChild* aPBackgroundChild)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPBackgroundChild);
+
+ layout::PVsyncChild* actor = aPBackgroundChild->SendPVsyncConstructor();
+ layout::VsyncChild* child = static_cast<layout::VsyncChild*>(actor);
+ nsRefreshDriver::PVsyncActorCreated(child);
+ }
+
+private:
+ virtual ~VsyncChildCreateCallback() = default;
+
+ void ActorCreated(PBackgroundChild* aPBackgroundChild) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPBackgroundChild);
+ CreateVsyncActor(aPBackgroundChild);
+ }
+
+ void ActorFailed() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_CRASH("Failed To Create VsyncChild Actor");
+ }
+}; // VsyncChildCreateCallback
+NS_IMPL_ISUPPORTS(VsyncChildCreateCallback, nsIIPCBackgroundChildCreateCallback)
+
+} // namespace mozilla
+
+static RefreshDriverTimer* sRegularRateTimer;
+static InactiveRefreshDriverTimer* sThrottledRateTimer;
+
+#ifdef XP_WIN
+static int32_t sHighPrecisionTimerRequests = 0;
+// a bare pointer to avoid introducing a static constructor
+static nsITimer *sDisableHighPrecisionTimersTimer = nullptr;
+#endif
+
+static void
+CreateContentVsyncRefreshTimer(void*)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ // Create the PVsync actor child for vsync-base refresh timer.
+ // PBackgroundChild is created asynchronously. If PBackgroundChild is still
+ // unavailable, setup VsyncChildCreateCallback callback to handle the async
+ // connect. We will still use software timer before PVsync ready, and change
+ // to use hw timer when the connection is done. Please check
+ // VsyncChildCreateCallback::CreateVsyncActor() and
+ // nsRefreshDriver::PVsyncActorCreated().
+ PBackgroundChild* backgroundChild = BackgroundChild::GetForCurrentThread();
+ if (backgroundChild) {
+ // If we already have PBackgroundChild, create the
+ // child VsyncRefreshDriverTimer here.
+ VsyncChildCreateCallback::CreateVsyncActor(backgroundChild);
+ return;
+ }
+ // Setup VsyncChildCreateCallback callback
+ RefPtr<nsIIPCBackgroundChildCreateCallback> callback = new VsyncChildCreateCallback();
+ if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
+ MOZ_CRASH("PVsync actor create failed!");
+ }
+}
+
+static void
+CreateVsyncRefreshTimer()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PodArrayZero(sJankLevels);
+ // Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is
+ // ready.
+ gfxPrefs::GetSingleton();
+
+ if (gfxPlatform::IsInLayoutAsapMode()) {
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Make sure all vsync systems are ready.
+ gfxPlatform::GetPlatform();
+ // In parent process, we don't need to use ipc. We can create the
+ // VsyncRefreshDriverTimer directly.
+ sRegularRateTimer = new VsyncRefreshDriverTimer();
+ return;
+ }
+
+ // If this process is not created by NUWA, just create the vsync timer here.
+ CreateContentVsyncRefreshTimer(nullptr);
+}
+
+static uint32_t
+GetFirstFrameDelay(imgIRequest* req)
+{
+ nsCOMPtr<imgIContainer> container;
+ if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
+ return 0;
+ }
+
+ // If this image isn't animated, there isn't a first frame delay.
+ int32_t delay = container->GetFirstFrameDelay();
+ if (delay < 0)
+ return 0;
+
+ return static_cast<uint32_t>(delay);
+}
+
+/* static */ void
+nsRefreshDriver::Shutdown()
+{
+ // clean up our timers
+ delete sRegularRateTimer;
+ delete sThrottledRateTimer;
+
+ sRegularRateTimer = nullptr;
+ sThrottledRateTimer = nullptr;
+
+#ifdef XP_WIN
+ if (sDisableHighPrecisionTimersTimer) {
+ sDisableHighPrecisionTimersTimer->Cancel();
+ NS_RELEASE(sDisableHighPrecisionTimersTimer);
+ timeEndPeriod(1);
+ } else if (sHighPrecisionTimerRequests) {
+ timeEndPeriod(1);
+ }
+#endif
+}
+
+/* static */ int32_t
+nsRefreshDriver::DefaultInterval()
+{
+ return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate());
+}
+
+// Compute the interval to use for the refresh driver timer, in milliseconds.
+// outIsDefault indicates that rate was not explicitly set by the user
+// so we might choose other, more appropriate rates (e.g. vsync, etc)
+// layout.frame_rate=0 indicates "ASAP mode".
+// In ASAP mode rendering is iterated as fast as possible (typically for stress testing).
+// A target rate of 10k is used internally instead of special-handling 0.
+// Backends which block on swap/present/etc should try to not block
+// when layout.frame_rate=0 - to comply with "ASAP" as much as possible.
+double
+nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const
+{
+ int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
+ if (rate < 0) {
+ rate = gfxPlatform::GetDefaultFrameRate();
+ if (outIsDefault) {
+ *outIsDefault = true;
+ }
+ } else {
+ if (outIsDefault) {
+ *outIsDefault = false;
+ }
+ }
+
+ if (rate == 0) {
+ rate = 10000;
+ }
+
+ return 1000.0 / rate;
+}
+
+/* static */ double
+nsRefreshDriver::GetThrottledTimerInterval()
+{
+ int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
+ if (rate <= 0) {
+ rate = DEFAULT_THROTTLED_FRAME_RATE;
+ }
+ return 1000.0 / rate;
+}
+
+/* static */ mozilla::TimeDuration
+nsRefreshDriver::GetMinRecomputeVisibilityInterval()
+{
+ int32_t interval =
+ Preferences::GetInt("layout.visibility.min-recompute-interval-ms", -1);
+ if (interval <= 0) {
+ interval = DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS;
+ }
+ return TimeDuration::FromMilliseconds(interval);
+}
+
+/* static */ mozilla::TimeDuration
+nsRefreshDriver::GetMinNotifyIntersectionObserversInterval()
+{
+ int32_t interval =
+ Preferences::GetInt("layout.visibility.min-notify-intersection-observers-interval-ms", -1);
+ if (interval <= 0) {
+ interval = DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS;
+ }
+ return TimeDuration::FromMilliseconds(interval);
+}
+
+double
+nsRefreshDriver::GetRefreshTimerInterval() const
+{
+ return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
+}
+
+RefreshDriverTimer*
+nsRefreshDriver::ChooseTimer() const
+{
+ if (mThrottled) {
+ if (!sThrottledRateTimer)
+ sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(),
+ DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
+ return sThrottledRateTimer;
+ }
+
+ if (!sRegularRateTimer) {
+ bool isDefault = true;
+ double rate = GetRegularTimerInterval(&isDefault);
+
+ // Try to use vsync-base refresh timer first for sRegularRateTimer.
+ CreateVsyncRefreshTimer();
+
+ if (!sRegularRateTimer) {
+ sRegularRateTimer = new StartupRefreshDriverTimer(rate);
+ }
+ }
+ return sRegularRateTimer;
+}
+
+nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
+ : mActiveTimer(nullptr),
+ mReflowCause(nullptr),
+ mStyleCause(nullptr),
+ mPresContext(aPresContext),
+ mRootRefresh(nullptr),
+ mPendingTransaction(0),
+ mCompletedTransaction(0),
+ mFreezeCount(0),
+ mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
+ GetThrottledTimerInterval())),
+ mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
+ mMinNotifyIntersectionObserversInterval(
+ GetMinNotifyIntersectionObserversInterval()),
+ mThrottled(false),
+ mNeedToRecomputeVisibility(false),
+ mTestControllingRefreshes(false),
+ mViewManagerFlushIsPending(false),
+ mRequestedHighPrecision(false),
+ mInRefresh(false),
+ mWaitingForTransaction(false),
+ mSkippedPaints(false),
+ mResizeSuppressed(false),
+ mWarningThreshold(REFRESH_WAIT_WARNING)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPresContext,
+ "Need a pres context to tell us to call Disconnect() later "
+ "and decrement sRefreshDriverCount.");
+ mMostRecentRefreshEpochTime = JS_Now();
+ mMostRecentRefresh = TimeStamp::Now();
+ mMostRecentTick = mMostRecentRefresh;
+ mNextThrottledFrameRequestTick = mMostRecentTick;
+ mNextRecomputeVisibilityTick = mMostRecentTick;
+ mNextNotifyIntersectionObserversTick = mMostRecentTick;
+
+ ++sRefreshDriverCount;
+}
+
+nsRefreshDriver::~nsRefreshDriver()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(ObserverCount() == 0,
+ "observers should have unregistered");
+ MOZ_ASSERT(!mActiveTimer, "timer should be gone");
+ MOZ_ASSERT(!mPresContext,
+ "Should have called Disconnect() and decremented "
+ "sRefreshDriverCount!");
+
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ }
+ for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
+ shell->InvalidatePresShellIfHidden();
+ }
+ mPresShellsToInvalidateIfHidden.Clear();
+
+ profiler_free_backtrace(mStyleCause);
+ profiler_free_backtrace(mReflowCause);
+}
+
+// Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
+// for description.
+void
+nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds)
+{
+ // ensure that we're removed from our driver
+ StopTimer();
+
+ if (!mTestControllingRefreshes) {
+ mMostRecentRefreshEpochTime = JS_Now();
+ mMostRecentRefresh = TimeStamp::Now();
+
+ mTestControllingRefreshes = true;
+ if (mWaitingForTransaction) {
+ // Disable any refresh driver throttling when entering test mode
+ mWaitingForTransaction = false;
+ mSkippedPaints = false;
+ mWarningThreshold = REFRESH_WAIT_WARNING;
+ }
+ }
+
+ mMostRecentRefreshEpochTime += aMilliseconds * 1000;
+ mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
+
+ mozilla::dom::AutoNoJSAPI nojsapi;
+ DoTick();
+}
+
+void
+nsRefreshDriver::RestoreNormalRefresh()
+{
+ mTestControllingRefreshes = false;
+ EnsureTimerStarted(eAllowTimeToGoBackwards);
+ mCompletedTransaction = mPendingTransaction;
+}
+
+TimeStamp
+nsRefreshDriver::MostRecentRefresh() const
+{
+ const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
+
+ return mMostRecentRefresh;
+}
+
+int64_t
+nsRefreshDriver::MostRecentRefreshEpochTime() const
+{
+ const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
+
+ return mMostRecentRefreshEpochTime;
+}
+
+bool
+nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ ObserverArray& array = ArrayFor(aFlushType);
+ bool success = array.AppendElement(aObserver) != nullptr;
+ EnsureTimerStarted();
+ return success;
+}
+
+bool
+nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ ObserverArray& array = ArrayFor(aFlushType);
+ return array.RemoveElement(aObserver);
+}
+
+void
+nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ mPostRefreshObservers.AppendElement(aObserver);
+}
+
+void
+nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ mPostRefreshObservers.RemoveElement(aObserver);
+}
+
+bool
+nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
+{
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay == 0) {
+ if (!mRequests.PutEntry(aRequest)) {
+ return false;
+ }
+ } else {
+ ImageStartData* start = mStartTable.Get(delay);
+ if (!start) {
+ start = new ImageStartData();
+ mStartTable.Put(delay, start);
+ }
+ start->mEntries.PutEntry(aRequest);
+ }
+
+ EnsureTimerStarted();
+
+ return true;
+}
+
+void
+nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
+{
+ // Try to remove from both places, just in case, because we can't tell
+ // whether RemoveEntry() succeeds.
+ mRequests.RemoveEntry(aRequest);
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay != 0) {
+ ImageStartData* start = mStartTable.Get(delay);
+ if (start) {
+ start->mEntries.RemoveEntry(aRequest);
+ }
+ }
+}
+
+void
+nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags)
+{
+ if (mTestControllingRefreshes)
+ return;
+
+ // will it already fire, and no other changes needed?
+ if (mActiveTimer && !(aFlags & eForceAdjustTimer))
+ return;
+
+ if (IsFrozen() || !mPresContext) {
+ // If we don't want to start it now, or we've been disconnected.
+ StopTimer();
+ return;
+ }
+
+ if (mPresContext->Document()->IsBeingUsedAsImage()) {
+ // Image documents receive ticks from clients' refresh drivers.
+ // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
+ // they receive refresh-driver ticks from their client docs (bug 1107252).
+ nsIURI* uri = mPresContext->Document()->GetDocumentURI();
+ if (!uri || !IsFontTableURI(uri)) {
+ MOZ_ASSERT(!mActiveTimer,
+ "image doc refresh driver should never have its own timer");
+ return;
+ }
+ }
+
+ // We got here because we're either adjusting the time *or* we're
+ // starting it for the first time. Add to the right timer,
+ // prehaps removing it from a previously-set one.
+ RefreshDriverTimer *newTimer = ChooseTimer();
+ if (newTimer != mActiveTimer) {
+ if (mActiveTimer)
+ mActiveTimer->RemoveRefreshDriver(this);
+ mActiveTimer = newTimer;
+ mActiveTimer->AddRefreshDriver(this);
+ }
+
+ // When switching from an inactive timer to an active timer, the root
+ // refresh driver is skipped due to being set to the content refresh
+ // driver's timestamp. In case of EnsureTimerStarted is called from
+ // ScheduleViewManagerFlush, we should avoid this behavior to flush
+ // a paint in the same tick on the root refresh driver.
+ if (aFlags & eNeverAdjustTimer) {
+ return;
+ }
+
+ // Since the different timers are sampled at different rates, when switching
+ // timers, the most recent refresh of the new timer may be *before* the
+ // most recent refresh of the old timer. However, the refresh driver time
+ // should not go backwards so we clamp the most recent refresh time.
+ //
+ // The one exception to this is when we are restoring the refresh driver
+ // from test control in which case the time is expected to go backwards
+ // (see bug 1043078).
+ mMostRecentRefresh =
+ aFlags & eAllowTimeToGoBackwards
+ ? mActiveTimer->MostRecentRefresh()
+ : std::max(mActiveTimer->MostRecentRefresh(), mMostRecentRefresh);
+ mMostRecentRefreshEpochTime =
+ aFlags & eAllowTimeToGoBackwards
+ ? mActiveTimer->MostRecentRefreshEpochTime()
+ : std::max(mActiveTimer->MostRecentRefreshEpochTime(),
+ mMostRecentRefreshEpochTime);
+}
+
+void
+nsRefreshDriver::StopTimer()
+{
+ if (!mActiveTimer)
+ return;
+
+ mActiveTimer->RemoveRefreshDriver(this);
+ mActiveTimer = nullptr;
+
+ if (mRequestedHighPrecision) {
+ SetHighPrecisionTimersEnabled(false);
+ }
+}
+
+#ifdef XP_WIN
+static void
+DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure)
+{
+ timeEndPeriod(1);
+ NS_RELEASE(sDisableHighPrecisionTimersTimer);
+}
+#endif
+
+void
+nsRefreshDriver::ConfigureHighPrecision()
+{
+ bool haveUnthrottledFrameRequestCallbacks =
+ mFrameRequestCallbackDocs.Length() > 0;
+
+ // if the only change that's needed is that we need high precision,
+ // then just set that
+ if (!mThrottled && !mRequestedHighPrecision &&
+ haveUnthrottledFrameRequestCallbacks) {
+ SetHighPrecisionTimersEnabled(true);
+ } else if (mRequestedHighPrecision && !haveUnthrottledFrameRequestCallbacks) {
+ SetHighPrecisionTimersEnabled(false);
+ }
+}
+
+void
+nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable)
+{
+ LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false");
+
+ if (aEnable) {
+ NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!");
+#ifdef XP_WIN
+ if (++sHighPrecisionTimerRequests == 1) {
+ // If we had a timer scheduled to disable it, that means that it's already
+ // enabled; just cancel the timer. Otherwise, really enable it.
+ if (sDisableHighPrecisionTimersTimer) {
+ sDisableHighPrecisionTimersTimer->Cancel();
+ NS_RELEASE(sDisableHighPrecisionTimersTimer);
+ } else {
+ timeBeginPeriod(1);
+ }
+ }
+#endif
+ mRequestedHighPrecision = true;
+ } else {
+ NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!");
+#ifdef XP_WIN
+ if (--sHighPrecisionTimerRequests == 0) {
+ // Don't jerk us around between high precision and low precision
+ // timers; instead, only allow leaving high precision timers
+ // after 90 seconds. This is arbitrary, but hopefully good
+ // enough.
+ NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !");
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (timer) {
+ timer.forget(&sDisableHighPrecisionTimersTimer);
+ sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback,
+ nullptr,
+ 90 * 1000,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ // might happen if we're shutting down XPCOM; just drop the time period down
+ // immediately
+ timeEndPeriod(1);
+ }
+ }
+#endif
+ mRequestedHighPrecision = false;
+ }
+}
+
+uint32_t
+nsRefreshDriver::ObserverCount() const
+{
+ uint32_t sum = 0;
+ for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
+ sum += mObservers[i].Length();
+ }
+
+ // Even while throttled, we need to process layout and style changes. Style
+ // changes can trigger transitions which fire events when they complete, and
+ // layout changes can affect media queries on child documents, triggering
+ // style changes, etc.
+ sum += mStyleFlushObservers.Length();
+ sum += mLayoutFlushObservers.Length();
+ sum += mPendingEvents.Length();
+ sum += mFrameRequestCallbackDocs.Length();
+ sum += mThrottledFrameRequestCallbackDocs.Length();
+ sum += mViewManagerFlushIsPending;
+ return sum;
+}
+
+uint32_t
+nsRefreshDriver::ImageRequestCount() const
+{
+ uint32_t count = 0;
+ for (auto iter = mStartTable.ConstIter(); !iter.Done(); iter.Next()) {
+ count += iter.UserData()->mEntries.Count();
+ }
+ return count + mRequests.Count();
+}
+
+nsRefreshDriver::ObserverArray&
+nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
+{
+ switch (aFlushType) {
+ case Flush_Style:
+ return mObservers[0];
+ case Flush_Layout:
+ return mObservers[1];
+ case Flush_Display:
+ return mObservers[2];
+ default:
+ MOZ_ASSERT(false, "bad flush type");
+ return *static_cast<ObserverArray*>(nullptr);
+ }
+}
+
+/*
+ * nsITimerCallback implementation
+ */
+
+void
+nsRefreshDriver::DoTick()
+{
+ NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?");
+ NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
+ NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
+ "Shouldn't have a JSContext on the stack");
+
+ if (mTestControllingRefreshes) {
+ Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh);
+ } else {
+ Tick(JS_Now(), TimeStamp::Now());
+ }
+}
+
+struct DocumentFrameCallbacks {
+ explicit DocumentFrameCallbacks(nsIDocument* aDocument) :
+ mDocument(aDocument)
+ {}
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsIDocument::FrameRequestCallbackList mCallbacks;
+};
+
+static nsDocShell* GetDocShell(nsPresContext* aPresContext)
+{
+ return static_cast<nsDocShell*>(aPresContext->GetDocShell());
+}
+
+static bool
+HasPendingAnimations(nsIPresShell* aShell)
+{
+ nsIDocument* doc = aShell->GetDocument();
+ if (!doc) {
+ return false;
+ }
+
+ PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
+ return tracker && tracker->HasPendingAnimations();
+}
+
+/**
+ * Return a list of all the child docShells in a given root docShell that are
+ * visible and are recording markers for the profilingTimeline
+ */
+static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell,
+ nsTArray<nsDocShell*>& aShells)
+{
+ if (!aRootDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || timelines->IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = aRootDocShell->GetDocShellEnumerator(
+ nsIDocShellTreeItem::typeAll,
+ nsIDocShell::ENUMERATE_BACKWARDS,
+ getter_AddRefs(enumerator));
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> curItem;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> curSupports;
+ enumerator->GetNext(getter_AddRefs(curSupports));
+ curItem = do_QueryInterface(curSupports);
+
+ if (!curItem || !curItem->GetRecordProfileTimelineMarkers()) {
+ continue;
+ }
+
+ nsDocShell* shell = static_cast<nsDocShell*>(curItem.get());
+ bool isVisible = false;
+ shell->GetVisibility(&isVisible);
+ if (!isVisible) {
+ continue;
+ }
+
+ aShells.AppendElement(shell);
+ }
+}
+
+static void
+TakeFrameRequestCallbacksFrom(nsIDocument* aDocument,
+ nsTArray<DocumentFrameCallbacks>& aTarget)
+{
+ aTarget.AppendElement(aDocument);
+ aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
+}
+
+void
+nsRefreshDriver::DispatchPendingEvents()
+{
+ // Swap out the current pending events
+ nsTArray<PendingEvent> pendingEvents(Move(mPendingEvents));
+ for (PendingEvent& event : pendingEvents) {
+ bool dummy;
+ event.mTarget->DispatchEvent(event.mEvent, &dummy);
+ }
+}
+
+static bool
+CollectDocuments(nsIDocument* aDocument, void* aDocArray)
+{
+ static_cast<nsCOMArray<nsIDocument>*>(aDocArray)->AppendObject(aDocument);
+ aDocument->EnumerateSubDocuments(CollectDocuments, aDocArray);
+ return true;
+}
+
+void
+nsRefreshDriver::DispatchAnimationEvents()
+{
+ if (!mPresContext) {
+ return;
+ }
+
+ nsCOMArray<nsIDocument> documents;
+ CollectDocuments(mPresContext->Document(), &documents);
+
+ for (int32_t i = 0; i < documents.Count(); ++i) {
+ nsIDocument* doc = documents[i];
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ continue;
+ }
+
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+ if (!context || context->RefreshDriver() != this) {
+ continue;
+ }
+
+ context->TransitionManager()->SortEvents();
+ context->AnimationManager()->SortEvents();
+
+ // Dispatch transition events first since transitions conceptually sit
+ // below animations in terms of compositing order.
+ context->TransitionManager()->DispatchEvents();
+ // Check that the presshell has not been destroyed
+ if (context->GetPresShell()) {
+ context->AnimationManager()->DispatchEvents();
+ }
+ }
+}
+
+void
+nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime)
+{
+ // Grab all of our frame request callbacks up front.
+ nsTArray<DocumentFrameCallbacks>
+ frameRequestCallbacks(mFrameRequestCallbackDocs.Length() +
+ mThrottledFrameRequestCallbackDocs.Length());
+
+ // First, grab throttled frame request callbacks.
+ {
+ nsTArray<nsIDocument*> docsToRemove;
+
+ // We always tick throttled frame requests if the entire refresh driver is
+ // throttled, because in that situation throttled frame requests tick at the
+ // same frequency as non-throttled frame requests.
+ bool tickThrottledFrameRequests = mThrottled;
+
+ if (!tickThrottledFrameRequests &&
+ aNowTime >= mNextThrottledFrameRequestTick) {
+ mNextThrottledFrameRequestTick = aNowTime + mThrottledFrameRequestInterval;
+ tickThrottledFrameRequests = true;
+ }
+
+ for (nsIDocument* doc : mThrottledFrameRequestCallbackDocs) {
+ if (tickThrottledFrameRequests) {
+ // We're ticking throttled documents, so grab this document's requests.
+ // We don't bother appending to docsToRemove because we're going to
+ // clear mThrottledFrameRequestCallbackDocs anyway.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ } else if (!doc->ShouldThrottleFrameRequests()) {
+ // This document is no longer throttled, so grab its requests even
+ // though we're not ticking throttled frame requests right now. If
+ // this is the first unthrottled document with frame requests, we'll
+ // enter high precision mode the next time the callback is scheduled.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ docsToRemove.AppendElement(doc);
+ }
+ }
+
+ // Remove all the documents we're ticking from
+ // mThrottledFrameRequestCallbackDocs so they can be readded as needed.
+ if (tickThrottledFrameRequests) {
+ mThrottledFrameRequestCallbackDocs.Clear();
+ } else {
+ // XXX(seth): We're using this approach to avoid concurrent modification
+ // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
+ // zero elements or a very small number, so this should be OK in practice.
+ for (nsIDocument* doc : docsToRemove) {
+ mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
+ }
+ }
+ }
+
+ // Now grab unthrottled frame request callbacks.
+ for (nsIDocument* doc : mFrameRequestCallbackDocs) {
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ }
+
+ // Reset mFrameRequestCallbackDocs so they can be readded as needed.
+ mFrameRequestCallbackDocs.Clear();
+
+ if (!frameRequestCallbacks.IsEmpty()) {
+ profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START);
+ for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
+ // XXXbz Bug 863140: GetInnerWindow can return the outer
+ // window in some cases.
+ nsPIDOMWindowInner* innerWindow =
+ docCallbacks.mDocument->GetInnerWindow();
+ DOMHighResTimeStamp timeStamp = 0;
+ if (innerWindow && innerWindow->IsInnerWindow()) {
+ mozilla::dom::Performance* perf = innerWindow->GetPerformance();
+ if (perf) {
+ timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);
+ }
+ // else window is partially torn down already
+ }
+ for (auto& callback : docCallbacks.mCallbacks) {
+ callback->Call(timeStamp);
+ }
+ }
+ profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END);
+ }
+}
+
+void
+nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
+{
+ NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
+ "Shouldn't have a JSContext on the stack");
+
+ if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
+ NS_ERROR("Refresh driver should not run during plugin call!");
+ // Try to survive this by just ignoring the refresh tick.
+ return;
+ }
+
+ PROFILER_LABEL("nsRefreshDriver", "Tick",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // We're either frozen or we were disconnected (likely in the middle
+ // of a tick iteration). Just do nothing here, since our
+ // prescontext went away.
+ if (IsFrozen() || !mPresContext) {
+ return;
+ }
+
+ // We can have a race condition where the vsync timestamp
+ // is before the most recent refresh due to a forced refresh.
+ // The underlying assumption is that the refresh driver tick can only
+ // go forward in time, not backwards. To prevent the refresh
+ // driver from going back in time, just skip this tick and
+ // wait until the next tick.
+ if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) {
+ return;
+ }
+
+ TimeStamp previousRefresh = mMostRecentRefresh;
+
+ mMostRecentRefresh = aNowTime;
+ mMostRecentRefreshEpochTime = aNowEpoch;
+
+ if (IsWaitingForPaint(aNowTime)) {
+ // We're currently suspended waiting for earlier Tick's to
+ // be completed (on the Compositor). Mark that we missed the paint
+ // and keep waiting.
+ return;
+ }
+ mMostRecentTick = aNowTime;
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ }
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+
+ nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
+ if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
+ // Things are being destroyed, or we no longer have any observers.
+ // We don't want to stop the timer when observers are initially
+ // removed, because sometimes observers can be added and removed
+ // often depending on what other things are going on and in that
+ // situation we don't want to thrash our timer. So instead we
+ // wait until we get a Notify() call when we have no observers
+ // before stopping the timer.
+ StopTimer();
+ return;
+ }
+
+ mResizeSuppressed = false;
+
+ AutoRestore<bool> restoreInRefresh(mInRefresh);
+ mInRefresh = true;
+
+ AutoRestore<TimeStamp> restoreTickStart(mTickStart);
+ mTickStart = TimeStamp::Now();
+
+ gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
+
+ // We want to process any pending APZ metrics ahead of their positions
+ // in the queue. This will prevent us from spending precious time
+ // painting a stale displayport.
+ if (gfxPrefs::APZPeekMessages()) {
+ nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages();
+ }
+
+ /*
+ * The timer holds a reference to |this| while calling |Notify|.
+ * However, implementations of |WillRefresh| are permitted to destroy
+ * the pres context, which will cause our |mPresContext| to become
+ * null. If this happens, we must stop notifying observers.
+ */
+ for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
+ ObserverArray::EndLimitedIterator etor(mObservers[i]);
+ while (etor.HasMore()) {
+ RefPtr<nsARefreshObserver> obs = etor.GetNext();
+ obs->WillRefresh(aNowTime);
+
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ }
+
+ if (i == 0) {
+ // This is the Flush_Style case.
+
+ DispatchAnimationEvents();
+ DispatchPendingEvents();
+ RunFrameRequestCallbacks(aNowTime);
+
+ if (mPresContext && mPresContext->GetPresShell()) {
+ bool tracingStyleFlush = false;
+ AutoTArray<nsIPresShell*, 16> observers;
+ observers.AppendElements(mStyleFlushObservers);
+ for (uint32_t j = observers.Length();
+ j && mPresContext && mPresContext->GetPresShell(); --j) {
+ // Make sure to not process observers which might have been removed
+ // during previous iterations.
+ nsIPresShell* shell = observers[j - 1];
+ if (!mStyleFlushObservers.Contains(shell))
+ continue;
+
+ if (!tracingStyleFlush) {
+ tracingStyleFlush = true;
+ profiler_tracing("Paint", "Styles", mStyleCause, TRACING_INTERVAL_START);
+ mStyleCause = nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell);
+ mStyleFlushObservers.RemoveElement(shell);
+ RestyleManagerHandle restyleManager =
+ shell->GetPresContext()->RestyleManager();
+ restyleManager->SetObservingRefreshDriver(false);
+ shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to (though it might still be waiting on
+ // a layout flush).
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ presContext->NotifyFontFaceSetOnRefresh();
+ }
+ mNeedToRecomputeVisibility = true;
+ }
+
+
+ if (tracingStyleFlush) {
+ profiler_tracing("Paint", "Styles", TRACING_INTERVAL_END);
+ }
+ }
+ } else if (i == 1) {
+ // This is the Flush_Layout case.
+ bool tracingLayoutFlush = false;
+ AutoTArray<nsIPresShell*, 16> observers;
+ observers.AppendElements(mLayoutFlushObservers);
+ for (uint32_t j = observers.Length();
+ j && mPresContext && mPresContext->GetPresShell(); --j) {
+ // Make sure to not process observers which might have been removed
+ // during previous iterations.
+ nsIPresShell* shell = observers[j - 1];
+ if (!mLayoutFlushObservers.Contains(shell))
+ continue;
+
+ if (!tracingLayoutFlush) {
+ tracingLayoutFlush = true;
+ profiler_tracing("Paint", "Reflow", mReflowCause, TRACING_INTERVAL_START);
+ mReflowCause = nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell);
+ mLayoutFlushObservers.RemoveElement(shell);
+ shell->mReflowScheduled = false;
+ shell->mSuppressInterruptibleReflows = false;
+ mozFlushType flushType = HasPendingAnimations(shell)
+ ? Flush_Layout
+ : Flush_InterruptibleLayout;
+ shell->FlushPendingNotifications(ChangesToFlush(flushType, false));
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to.
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ presContext->NotifyFontFaceSetOnRefresh();
+ }
+ mNeedToRecomputeVisibility = true;
+ }
+
+ if (tracingLayoutFlush) {
+ profiler_tracing("Paint", "Reflow", TRACING_INTERVAL_END);
+ }
+ }
+
+ // The pres context may be destroyed during we do the flushing.
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ }
+
+ // Recompute approximate frame visibility if it's necessary and enough time
+ // has passed since the last time we did it.
+ if (mNeedToRecomputeVisibility && !mThrottled &&
+ aNowTime >= mNextRecomputeVisibilityTick &&
+ !presShell->IsPaintingSuppressed()) {
+ mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
+ mNeedToRecomputeVisibility = false;
+
+ presShell->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+
+ bool notifyIntersectionObservers = false;
+ if (aNowTime >= mNextNotifyIntersectionObserversTick) {
+ mNextNotifyIntersectionObserversTick =
+ aNowTime + mMinNotifyIntersectionObserversInterval;
+ notifyIntersectionObservers = true;
+ }
+ nsCOMArray<nsIDocument> documents;
+ CollectDocuments(mPresContext->Document(), &documents);
+ for (int32_t i = 0; i < documents.Count(); ++i) {
+ nsIDocument* doc = documents[i];
+ doc->UpdateIntersectionObservations();
+ if (notifyIntersectionObservers) {
+ doc->ScheduleIntersectionObserverNotification();
+ }
+ }
+
+ /*
+ * Perform notification to imgIRequests subscribed to listen
+ * for refresh events.
+ */
+
+ for (auto iter = mStartTable.Iter(); !iter.Done(); iter.Next()) {
+ const uint32_t& delay = iter.Key();
+ ImageStartData* data = iter.UserData();
+
+ if (data->mStartTime) {
+ TimeStamp& start = *data->mStartTime;
+ TimeDuration prev = previousRefresh - start;
+ TimeDuration curr = aNowTime - start;
+ uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
+
+ // We want to trigger images' refresh if we've just crossed over a
+ // multiple of the first image's start time. If so, set the animation
+ // start time to the nearest multiple of the delay and move all the
+ // images in this table to the main requests table.
+ if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
+ mozilla::TimeStamp desired =
+ start + TimeDuration::FromMilliseconds(prevMultiple * delay);
+ BeginRefreshingImages(data->mEntries, desired);
+ }
+ } else {
+ // This is the very first time we've drawn images with this time delay.
+ // Set the animation start time to "now" and move all the images in this
+ // table to the main requests table.
+ mozilla::TimeStamp desired = aNowTime;
+ BeginRefreshingImages(data->mEntries, desired);
+ data->mStartTime.emplace(aNowTime);
+ }
+ }
+
+ if (mRequests.Count()) {
+ // RequestRefresh may run scripts, so it's not safe to directly call it
+ // while using a hashtable enumerator to enumerate mRequests in case
+ // script modifies the hashtable. Instead, we build a (local) array of
+ // images to refresh, and then we refresh each image in that array.
+ nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ nsISupportsHashKey* entry = iter.Get();
+ auto req = static_cast<imgIRequest*>(entry->GetKey());
+ MOZ_ASSERT(req, "Unable to retrieve the image request");
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ imagesToRefresh.AppendElement(image.forget());
+ }
+ }
+
+ for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
+ imagesToRefresh[i]->RequestRefresh(aNowTime);
+ }
+ }
+
+ for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
+ shell->InvalidatePresShellIfHidden();
+ }
+ mPresShellsToInvalidateIfHidden.Clear();
+
+ bool notifyGC = false;
+ if (mViewManagerFlushIsPending) {
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+ nsTArray<nsDocShell*> profilingDocShells;
+ GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
+ for (nsDocShell* docShell : profilingDocShells) {
+ // For the sake of the profile timeline's simplicity, this is flagged as
+ // paint even if it includes creating display lists
+ MOZ_ASSERT(timelines);
+ MOZ_ASSERT(timelines->HasConsumer(docShell));
+ timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::START);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Starting ProcessPendingUpdates\n");
+ }
+#endif
+
+ mViewManagerFlushIsPending = false;
+ RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
+ {
+ PaintTelemetry::AutoRecordPaint record;
+ vm->ProcessPendingUpdates();
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Ending ProcessPendingUpdates\n");
+ }
+#endif
+
+ for (nsDocShell* docShell : profilingDocShells) {
+ MOZ_ASSERT(timelines);
+ MOZ_ASSERT(timelines->HasConsumer(docShell));
+ timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::END);
+ }
+
+ notifyGC = true;
+ }
+
+#ifndef ANDROID /* bug 1142079 */
+ mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
+#endif
+
+ nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
+ while (iter.HasMore()) {
+ nsAPostRefreshObserver* observer = iter.GetNext();
+ observer->DidRefresh();
+ }
+
+ ConfigureHighPrecision();
+
+ NS_ASSERTION(mInRefresh, "Still in refresh");
+
+ if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+ ScheduleViewManagerFlush();
+ }
+
+ if (notifyGC && nsContentUtils::XPConnect()) {
+ nsContentUtils::XPConnect()->NotifyDidPaint();
+ nsJSContext::NotifyDidPaint();
+ }
+}
+
+void
+nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
+ mozilla::TimeStamp aDesired)
+{
+ for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto req = static_cast<imgIRequest*>(iter.Get()->GetKey());
+ MOZ_ASSERT(req, "Unable to retrieve the image request");
+
+ mRequests.PutEntry(req);
+
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ image->SetAnimationStartTime(aDesired);
+ }
+ }
+ aEntries.Clear();
+}
+
+void
+nsRefreshDriver::Freeze()
+{
+ StopTimer();
+ mFreezeCount++;
+}
+
+void
+nsRefreshDriver::Thaw()
+{
+ NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
+
+ if (mFreezeCount > 0) {
+ mFreezeCount--;
+ }
+
+ if (mFreezeCount == 0) {
+ if (ObserverCount() || ImageRequestCount()) {
+ // FIXME: This isn't quite right, since our EnsureTimerStarted call
+ // updates our mMostRecentRefresh, but the DoRefresh call won't run
+ // and notify our observers until we get back to the event loop.
+ // Thus MostRecentRefresh() will lie between now and the DoRefresh.
+ NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
+ EnsureTimerStarted();
+ }
+ }
+}
+
+void
+nsRefreshDriver::FinishedWaitingForTransaction()
+{
+ mWaitingForTransaction = false;
+ if (mSkippedPaints &&
+ !IsInRefresh() &&
+ (ObserverCount() || ImageRequestCount())) {
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
+ DoRefresh();
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
+ }
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+}
+
+uint64_t
+nsRefreshDriver::GetTransactionId()
+{
+ ++mPendingTransaction;
+
+ if (mPendingTransaction >= mCompletedTransaction + 2 &&
+ !mWaitingForTransaction &&
+ !mTestControllingRefreshes) {
+ mWaitingForTransaction = true;
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+ }
+
+ return mPendingTransaction;
+}
+
+uint64_t
+nsRefreshDriver::LastTransactionId() const
+{
+ return mPendingTransaction;
+}
+
+void
+nsRefreshDriver::RevokeTransactionId(uint64_t aTransactionId)
+{
+ MOZ_ASSERT(aTransactionId == mPendingTransaction);
+ if (mPendingTransaction == mCompletedTransaction + 2 &&
+ mWaitingForTransaction) {
+ MOZ_ASSERT(!mSkippedPaints, "How did we skip a paint when we're in the middle of one?");
+ FinishedWaitingForTransaction();
+ }
+ mPendingTransaction--;
+}
+
+mozilla::TimeStamp
+nsRefreshDriver::GetTransactionStart()
+{
+ return mTickStart;
+}
+
+void
+nsRefreshDriver::NotifyTransactionCompleted(uint64_t aTransactionId)
+{
+ if (aTransactionId > mCompletedTransaction) {
+ if (mPendingTransaction > mCompletedTransaction + 1 &&
+ mWaitingForTransaction) {
+ mCompletedTransaction = aTransactionId;
+ FinishedWaitingForTransaction();
+ } else {
+ mCompletedTransaction = aTransactionId;
+ }
+ }
+}
+
+void
+nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime)
+{
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ if (mSkippedPaints) {
+ DoRefresh();
+ }
+}
+
+bool
+nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime)
+{
+ if (mTestControllingRefreshes) {
+ return false;
+ }
+
+ if (mWaitingForTransaction) {
+ if (mSkippedPaints && aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(mWarningThreshold * 1000))) {
+ // XXX - Bug 1303369 - too many false positives.
+ //gfxCriticalNote << "Refresh driver waiting for the compositor for "
+ // << (aTime - mMostRecentTick).ToSeconds()
+ // << " seconds.";
+ mWarningThreshold *= 2;
+ }
+
+ mSkippedPaints = true;
+ return true;
+ }
+
+ // Try find the 'root' refresh driver for the current window and check
+ // if that is waiting for a paint.
+ nsPresContext* pc = GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (rootContext) {
+ nsRefreshDriver *rootRefresh = rootContext->RefreshDriver();
+ if (rootRefresh && rootRefresh != this) {
+ if (rootRefresh->IsWaitingForPaint(aTime)) {
+ if (mRootRefresh != rootRefresh) {
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ }
+ rootRefresh->AddRefreshObserver(this, Flush_Style);
+ mRootRefresh = rootRefresh;
+ }
+ mSkippedPaints = true;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+nsRefreshDriver::SetThrottled(bool aThrottled)
+{
+ if (aThrottled != mThrottled) {
+ mThrottled = aThrottled;
+ if (mActiveTimer) {
+ // We want to switch our timer type here, so just stop and
+ // restart the timer.
+ EnsureTimerStarted(eForceAdjustTimer);
+ }
+ }
+}
+
+/*static*/ void
+nsRefreshDriver::PVsyncActorCreated(VsyncChild* aVsyncChild)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ auto* vsyncRefreshDriverTimer =
+ new VsyncRefreshDriverTimer(aVsyncChild);
+
+ // If we are using software timer, swap current timer to
+ // VsyncRefreshDriverTimer.
+ if (sRegularRateTimer) {
+ sRegularRateTimer->SwapRefreshDrivers(vsyncRefreshDriverTimer);
+ delete sRegularRateTimer;
+ }
+ sRegularRateTimer = vsyncRefreshDriverTimer;
+}
+
+void
+nsRefreshDriver::DoRefresh()
+{
+ // Don't do a refresh unless we're in a state where we should be refreshing.
+ if (!IsFrozen() && mPresContext && mActiveTimer) {
+ DoTick();
+ }
+}
+
+#ifdef DEBUG
+bool
+nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ ObserverArray& array = ArrayFor(aFlushType);
+ return array.Contains(aObserver);
+}
+#endif
+
+void
+nsRefreshDriver::ScheduleViewManagerFlush()
+{
+ NS_ASSERTION(mPresContext->IsRoot(),
+ "Should only schedule view manager flush on root prescontexts");
+ mViewManagerFlushIsPending = true;
+ EnsureTimerStarted(eNeverAdjustTimer);
+}
+
+void
+nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
+{
+ NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mFrameRequestCallbackDocs.NoIndex &&
+ mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mThrottledFrameRequestCallbackDocs.NoIndex,
+ "Don't schedule the same document multiple times");
+ if (aDocument->ShouldThrottleFrameRequests()) {
+ mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
+ } else {
+ mFrameRequestCallbackDocs.AppendElement(aDocument);
+ }
+
+ // make sure that the timer is running
+ ConfigureHighPrecision();
+ EnsureTimerStarted();
+}
+
+void
+nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
+{
+ mFrameRequestCallbackDocs.RemoveElement(aDocument);
+ mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);
+ ConfigureHighPrecision();
+ // No need to worry about restarting our timer in slack mode if it's already
+ // running; that will happen automatically when it fires.
+}
+
+void
+nsRefreshDriver::ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent)
+{
+ mPendingEvents.AppendElement(PendingEvent{aTarget, aEvent});
+ // make sure that the timer is running
+ EnsureTimerStarted();
+}
+
+void
+nsRefreshDriver::CancelPendingEvents(nsIDocument* aDocument)
+{
+ for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) {
+ if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
+ mPendingEvents.RemoveElementAt(i);
+ }
+ }
+}
+
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetIdleDeadlineHint()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sRegularRateTimer) {
+ return Nothing();
+ }
+
+ // For computing idleness of refresh drivers we only care about
+ // sRegularRateTimer, since we consider refresh drivers attached to
+ // sThrottledRateTimer to be inactive. This implies that tasks
+ // resulting from a tick on the sRegularRateTimer counts as being
+ // busy but tasks resulting from a tick on sThrottledRateTimer
+ // counts as being idle.
+ return sRegularRateTimer->GetIdleDeadlineHint();
+}
+
+void
+nsRefreshDriver::Disconnect()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StopTimer();
+
+ if (mPresContext) {
+ mPresContext = nullptr;
+ if (--sRefreshDriverCount == 0) {
+ Shutdown();
+ }
+ }
+}
+
+/* static */ bool
+nsRefreshDriver::IsJankCritical()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return sActiveVsyncTimers > 0;
+}
+
+/* static */ bool
+nsRefreshDriver::GetJankLevels(Vector<uint64_t>& aJank) {
+ aJank.clear();
+ return aJank.append(sJankLevels, ArrayLength(sJankLevels));
+}
+
+#undef LOG
diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h
new file mode 100644
index 000000000..b2dd9be4b
--- /dev/null
+++ b/layout/base/nsRefreshDriver.h
@@ -0,0 +1,523 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Code to notify things that animate before a refresh, at an appropriate
+ * refresh rate. (Perhaps temporary, until replaced by compositor.)
+ */
+
+#ifndef nsRefreshDriver_h_
+#define nsRefreshDriver_h_
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WeakPtr.h"
+#include "mozFlushType.h"
+#include "nsTObserverArray.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsTObserverArray.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "GeckoProfiler.h"
+#include "mozilla/layers/TransactionIdAllocator.h"
+
+class nsPresContext;
+class nsIPresShell;
+class nsIDocument;
+class imgIRequest;
+class nsIDOMEvent;
+class nsINode;
+
+namespace mozilla {
+class RefreshDriverTimer;
+namespace layout {
+class VsyncChild;
+} // namespace layout
+} // namespace mozilla
+
+/**
+ * An abstract base class to be implemented by callers wanting to be
+ * notified at refresh times. When nothing needs to be painted, callers
+ * may not be notified.
+ */
+class nsARefreshObserver {
+public:
+ // AddRef and Release signatures that match nsISupports. Implementors
+ // must implement reference counting, and those that do implement
+ // nsISupports will already have methods with the correct signature.
+ //
+ // The refresh driver does NOT hold references to refresh observers
+ // except while it is notifying them.
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
+};
+
+/**
+ * An abstract base class to be implemented by callers wanting to be notified
+ * that a refresh has occurred. Callers must ensure an observer is removed
+ * before it is destroyed.
+ */
+class nsAPostRefreshObserver {
+public:
+ virtual void DidRefresh() = 0;
+};
+
+class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
+ public nsARefreshObserver
+{
+public:
+ explicit nsRefreshDriver(nsPresContext *aPresContext);
+ ~nsRefreshDriver();
+
+ /**
+ * Methods for testing, exposed via nsIDOMWindowUtils. See
+ * nsIDOMWindowUtils.advanceTimeAndRefresh for description.
+ */
+ void AdvanceTimeAndRefresh(int64_t aMilliseconds);
+ void RestoreNormalRefresh();
+ void DoTick();
+ bool IsTestControllingRefreshesEnabled() const
+ {
+ return mTestControllingRefreshes;
+ }
+
+ /**
+ * Return the time of the most recent refresh. This is intended to be
+ * used by callers who want to start an animation now and want to know
+ * what time to consider the start of the animation. (This helps
+ * ensure that multiple animations started during the same event off
+ * the main event loop have the same start time.)
+ */
+ mozilla::TimeStamp MostRecentRefresh() const;
+ /**
+ * Same thing, but in microseconds since the epoch.
+ */
+ int64_t MostRecentRefreshEpochTime() const;
+
+ /**
+ * Add / remove refresh observers. Returns whether the operation
+ * succeeded.
+ *
+ * The flush type affects:
+ * + the order in which the observers are notified (lowest flush
+ * type to highest, in order registered)
+ * + (in the future) which observers are suppressed when the display
+ * doesn't require current position data or isn't currently
+ * painting, and, correspondingly, which get notified when there
+ * is a flush during such suppression
+ * and it must be either Flush_Style, Flush_Layout, or Flush_Display.
+ *
+ * The refresh driver does NOT own a reference to these observers;
+ * they must remove themselves before they are destroyed.
+ *
+ * The observer will be called even if there is no other activity.
+ */
+ bool AddRefreshObserver(nsARefreshObserver *aObserver,
+ mozFlushType aFlushType);
+ bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
+ mozFlushType aFlushType);
+
+ /**
+ * Add an observer that will be called after each refresh. The caller
+ * must remove the observer before it is deleted. This does not trigger
+ * refresh driver ticks.
+ */
+ void AddPostRefreshObserver(nsAPostRefreshObserver *aObserver);
+ void RemovePostRefreshObserver(nsAPostRefreshObserver *aObserver);
+
+ /**
+ * Add/Remove imgIRequest versions of observers.
+ *
+ * These are used for hooking into the refresh driver for
+ * controlling animated images.
+ *
+ * @note The refresh driver owns a reference to these listeners.
+ *
+ * @note Technically, imgIRequest objects are not nsARefreshObservers, but
+ * for controlling animated image repaint events, we subscribe the
+ * imgIRequests to the nsRefreshDriver for notification of paint events.
+ *
+ * @returns whether the operation succeeded, or void in the case of removal.
+ */
+ bool AddImageRequest(imgIRequest* aRequest);
+ void RemoveImageRequest(imgIRequest* aRequest);
+
+ /**
+ * Add / remove presshells that we should flush style and layout on
+ */
+ bool AddStyleFlushObserver(nsIPresShell* aShell) {
+ NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
+ "Double-adding style flush observer");
+ // We only get the cause for the first observer each frame because capturing
+ // a stack is expensive. This is still useful if (1) you're trying to remove
+ // all flushes for a particial frame or (2) the costly flush is triggered
+ // near the call site where the first observer is triggered.
+ if (!mStyleCause) {
+ mStyleCause = profiler_get_backtrace();
+ }
+ bool appended = mStyleFlushObservers.AppendElement(aShell) != nullptr;
+ EnsureTimerStarted();
+
+ return appended;
+ }
+ void RemoveStyleFlushObserver(nsIPresShell* aShell) {
+ mStyleFlushObservers.RemoveElement(aShell);
+ }
+ bool AddLayoutFlushObserver(nsIPresShell* aShell) {
+ NS_ASSERTION(!IsLayoutFlushObserver(aShell),
+ "Double-adding layout flush observer");
+ // We only get the cause for the first observer each frame because capturing
+ // a stack is expensive. This is still useful if (1) you're trying to remove
+ // all flushes for a particial frame or (2) the costly flush is triggered
+ // near the call site where the first observer is triggered.
+ if (!mReflowCause) {
+ mReflowCause = profiler_get_backtrace();
+ }
+ bool appended = mLayoutFlushObservers.AppendElement(aShell) != nullptr;
+ EnsureTimerStarted();
+ return appended;
+ }
+ void RemoveLayoutFlushObserver(nsIPresShell* aShell) {
+ mLayoutFlushObservers.RemoveElement(aShell);
+ }
+ bool IsLayoutFlushObserver(nsIPresShell* aShell) {
+ return mLayoutFlushObservers.Contains(aShell);
+ }
+ bool AddPresShellToInvalidateIfHidden(nsIPresShell* aShell) {
+ NS_ASSERTION(!mPresShellsToInvalidateIfHidden.Contains(aShell),
+ "Double-adding style flush observer");
+ bool appended = mPresShellsToInvalidateIfHidden.AppendElement(aShell) != nullptr;
+ EnsureTimerStarted();
+ return appended;
+ }
+ void RemovePresShellToInvalidateIfHidden(nsIPresShell* aShell) {
+ mPresShellsToInvalidateIfHidden.RemoveElement(aShell);
+ }
+
+ /**
+ * Remember whether our presshell's view manager needs a flush
+ */
+ void ScheduleViewManagerFlush();
+ void RevokeViewManagerFlush() {
+ mViewManagerFlushIsPending = false;
+ }
+ bool ViewManagerFlushIsPending() {
+ return mViewManagerFlushIsPending;
+ }
+
+ /**
+ * Add a document for which we have FrameRequestCallbacks
+ */
+ void ScheduleFrameRequestCallbacks(nsIDocument* aDocument);
+
+ /**
+ * Remove a document for which we have FrameRequestCallbacks
+ */
+ void RevokeFrameRequestCallbacks(nsIDocument* aDocument);
+
+ /**
+ * Queue a new event to dispatch in next tick before the style flush
+ */
+ void ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent);
+
+ /**
+ * Cancel all pending events scheduled by ScheduleEventDispatch which
+ * targets any node in aDocument.
+ */
+ void CancelPendingEvents(nsIDocument* aDocument);
+
+ /**
+ * Schedule a frame visibility update "soon", subject to the heuristics and
+ * throttling we apply to visibility updates.
+ */
+ void ScheduleFrameVisibilityUpdate() { mNeedToRecomputeVisibility = true; }
+
+ /**
+ * Tell the refresh driver that it is done driving refreshes and
+ * should stop its timer and forget about its pres context. This may
+ * be called from within a refresh.
+ */
+ void Disconnect();
+
+ bool IsFrozen() { return mFreezeCount > 0; }
+
+ /**
+ * Freeze the refresh driver. It should stop delivering future
+ * refreshes until thawed. Note that the number of calls to Freeze() must
+ * match the number of calls to Thaw() in order for the refresh driver to
+ * be un-frozen.
+ */
+ void Freeze();
+
+ /**
+ * Thaw the refresh driver. If the number of calls to Freeze() matches the
+ * number of calls to this function, the refresh driver should start
+ * delivering refreshes again.
+ */
+ void Thaw();
+
+ /**
+ * Throttle or unthrottle the refresh driver. This is done if the
+ * corresponding presshell is hidden or shown.
+ */
+ void SetThrottled(bool aThrottled);
+
+ /**
+ * Return the prescontext we were initialized with
+ */
+ nsPresContext* GetPresContext() const { return mPresContext; }
+
+ /**
+ * PBackgroundChild actor is created asynchronously in content process.
+ * We can't create vsync-based timers during PBackground startup. This
+ * function will be called when PBackgroundChild actor is created. Then we can
+ * do the pending vsync-based timer creation.
+ */
+ static void PVsyncActorCreated(mozilla::layout::VsyncChild* aVsyncChild);
+
+#ifdef DEBUG
+ /**
+ * Check whether the given observer is an observer for the given flush type
+ */
+ bool IsRefreshObserver(nsARefreshObserver *aObserver,
+ mozFlushType aFlushType);
+#endif
+
+ /**
+ * Default interval the refresh driver uses, in ms.
+ */
+ static int32_t DefaultInterval();
+
+ bool IsInRefresh() { return mInRefresh; }
+
+ void SetIsResizeSuppressed() { mResizeSuppressed = true; }
+ bool IsResizeSuppressed() const { return mResizeSuppressed; }
+
+ /**
+ * The latest value of process-wide jank levels.
+ *
+ * For each i, sJankLevels[i] counts the number of times delivery of
+ * vsync to the main thread has been delayed by at least 2^i
+ * ms. This data structure has been designed to make it easy to
+ * determine how much jank has taken place between two instants in
+ * time.
+ *
+ * Return `false` if `aJank` needs to be grown to accomodate the
+ * data but we didn't have enough memory.
+ */
+ static bool GetJankLevels(mozilla::Vector<uint64_t>& aJank);
+
+ // mozilla::layers::TransactionIdAllocator
+ uint64_t GetTransactionId() override;
+ uint64_t LastTransactionId() const override;
+ void NotifyTransactionCompleted(uint64_t aTransactionId) override;
+ void RevokeTransactionId(uint64_t aTransactionId) override;
+ mozilla::TimeStamp GetTransactionStart() override;
+
+ bool IsWaitingForPaint(mozilla::TimeStamp aTime);
+
+ // nsARefreshObserver
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return TransactionIdAllocator::AddRef(); }
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return TransactionIdAllocator::Release(); }
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ /**
+ * Compute the time when the currently active refresh driver timer
+ * will start its next tick.
+ *
+ * Returns 'Nothing' if the refresh driver timer hasn't been
+ * initialized or if we can't tell when the next tick will happen.
+ *
+ * Returns Some(TimeStamp()), i.e. the null time, if the next tick is late.
+ *
+ * Otherwise returns Some(TimeStamp(t)), where t is the time of the next tick.
+ *
+ * Using these three types of return values it is possible to
+ * estimate three different things about the idleness of the
+ * currently active group of refresh drivers. This information is
+ * used by nsThread to schedule lower priority "idle tasks".
+ *
+ * The 'Nothing' return value indicates to nsThread that the
+ * currently active refresh drivers will be idle for a time
+ * significantly longer than the current refresh rate and that it is
+ * free to schedule longer periods for executing idle tasks. This is the
+ * expected result when we aren't animating.
+
+ * Returning the null time indicates to nsThread that we are very
+ * busy and that it should definitely not schedule idle tasks at
+ * all. This is the expected result when we are animating, but
+ * aren't able to keep up with the animation and hence need to skip
+ * paints. Since catching up to missed paints will happen as soon as
+ * possible, this is the expected result if any of the refresh
+ * drivers attached to the current refresh driver misses a paint.
+ *
+ * Returning Some(TimeStamp(t)) indicates to nsThread that we will
+ * be idle until. This is usually the case when we're animating
+ * without skipping paints.
+ */
+ static mozilla::Maybe<mozilla::TimeStamp> GetIdleDeadlineHint();
+
+ bool SkippedPaints() const
+ {
+ return mSkippedPaints;
+ }
+
+private:
+ typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
+ typedef nsTHashtable<nsISupportsHashKey> RequestTable;
+ struct ImageStartData {
+ ImageStartData()
+ {
+ }
+
+ mozilla::Maybe<mozilla::TimeStamp> mStartTime;
+ RequestTable mEntries;
+ };
+ typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
+
+ void DispatchPendingEvents();
+ void DispatchAnimationEvents();
+ void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
+ void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
+
+ enum EnsureTimerStartedFlags {
+ eNone = 0,
+ eForceAdjustTimer = 1 << 0,
+ eAllowTimeToGoBackwards = 1 << 1,
+ eNeverAdjustTimer = 1 << 2,
+ };
+ void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone);
+ void StopTimer();
+
+ uint32_t ObserverCount() const;
+ uint32_t ImageRequestCount() const;
+ ObserverArray& ArrayFor(mozFlushType aFlushType);
+ // Trigger a refresh immediately, if haven't been disconnected or frozen.
+ void DoRefresh();
+
+ double GetRefreshTimerInterval() const;
+ double GetRegularTimerInterval(bool *outIsDefault = nullptr) const;
+ static double GetThrottledTimerInterval();
+
+ static mozilla::TimeDuration GetMinRecomputeVisibilityInterval();
+ static mozilla::TimeDuration GetMinNotifyIntersectionObserversInterval();
+
+ bool HaveFrameRequestCallbacks() const {
+ return mFrameRequestCallbackDocs.Length() != 0;
+ }
+
+ void FinishedWaitingForTransaction();
+
+ mozilla::RefreshDriverTimer* ChooseTimer() const;
+ mozilla::RefreshDriverTimer* mActiveTimer;
+
+ ProfilerBacktrace* mReflowCause;
+ ProfilerBacktrace* mStyleCause;
+
+ // nsPresContext passed in constructor and unset in Disconnect.
+ mozilla::WeakPtr<nsPresContext> mPresContext;
+
+ RefPtr<nsRefreshDriver> mRootRefresh;
+
+ // The most recently allocated transaction id.
+ uint64_t mPendingTransaction;
+ // The most recently completed transaction id.
+ uint64_t mCompletedTransaction;
+
+ uint32_t mFreezeCount;
+
+ // How long we wait between ticks for throttled (which generally means
+ // non-visible) documents registered with a non-throttled refresh driver.
+ const mozilla::TimeDuration mThrottledFrameRequestInterval;
+
+ // How long we wait, at a minimum, before recomputing approximate frame
+ // visibility information. This is a minimum because, regardless of this
+ // interval, we only recompute visibility when we've seen a layout or style
+ // flush since the last time we did it.
+ const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
+
+ const mozilla::TimeDuration mMinNotifyIntersectionObserversInterval;
+
+ bool mThrottled;
+ bool mNeedToRecomputeVisibility;
+ bool mTestControllingRefreshes;
+ bool mViewManagerFlushIsPending;
+ bool mRequestedHighPrecision;
+ bool mInRefresh;
+
+ // True if the refresh driver is suspended waiting for transaction
+ // id's to be returned and shouldn't do any work during Tick().
+ bool mWaitingForTransaction;
+ // True if Tick() was skipped because of mWaitingForTransaction and
+ // we should schedule a new Tick immediately when resumed instead
+ // of waiting until the next interval.
+ bool mSkippedPaints;
+
+ // True if view managers should delay any resize request until the
+ // next tick by the refresh driver. This flag will be reset at the
+ // start of every tick.
+ bool mResizeSuppressed;
+
+ int64_t mMostRecentRefreshEpochTime;
+ // Number of seconds that the refresh driver is blocked waiting for a compositor
+ // transaction to be completed before we append a note to the gfx critical log.
+ // The number is doubled every time the threshold is hit.
+ uint64_t mWarningThreshold;
+ mozilla::TimeStamp mMostRecentRefresh;
+ mozilla::TimeStamp mMostRecentTick;
+ mozilla::TimeStamp mTickStart;
+ mozilla::TimeStamp mNextThrottledFrameRequestTick;
+ mozilla::TimeStamp mNextRecomputeVisibilityTick;
+ mozilla::TimeStamp mNextNotifyIntersectionObserversTick;
+
+ // separate arrays for each flush type we support
+ ObserverArray mObservers[3];
+ RequestTable mRequests;
+ ImageStartTable mStartTable;
+
+ struct PendingEvent {
+ nsCOMPtr<nsINode> mTarget;
+ nsCOMPtr<nsIDOMEvent> mEvent;
+ };
+
+ AutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
+ AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
+ AutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden;
+ // nsTArray on purpose, because we want to be able to swap.
+ nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
+ nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
+ nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
+ nsTArray<PendingEvent> mPendingEvents;
+
+ void BeginRefreshingImages(RequestTable& aEntries,
+ mozilla::TimeStamp aDesired);
+
+ friend class mozilla::RefreshDriverTimer;
+
+ // turn on or turn off high precision based on various factors
+ void ConfigureHighPrecision();
+ void SetHighPrecisionTimersEnabled(bool aEnable);
+
+ static void Shutdown();
+
+ // `true` if we are currently in jank-critical mode.
+ //
+ // In jank-critical mode, any iteration of the event loop that takes
+ // more than 16ms to compute will cause an ongoing animation to miss
+ // frames.
+ //
+ // For simplicity, the current implementation assumes that we are
+ // in jank-critical mode if and only if the vsync driver has at least
+ // one observer.
+ static bool IsJankCritical();
+};
+
+#endif /* !defined(nsRefreshDriver_h_) */
diff --git a/layout/base/nsStyleChangeList.cpp b/layout/base/nsStyleChangeList.cpp
new file mode 100644
index 000000000..d47ee8d63
--- /dev/null
+++ b/layout/base/nsStyleChangeList.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a list of the recomputation that needs to be done in response to a
+ * style change
+ */
+
+#include "nsStyleChangeList.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsFrameManager.h"
+
+void
+nsStyleChangeList::AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChangeHint aHint)
+{
+ MOZ_ASSERT(aFrame || (aHint & nsChangeHint_ReconstructFrame),
+ "must have frame");
+ MOZ_ASSERT(aContent || !(aHint & nsChangeHint_ReconstructFrame),
+ "must have content");
+ // XXXbz we should make this take Element instead of nsIContent
+ MOZ_ASSERT(!aContent || aContent->IsElement() ||
+ // display:contents elements posts the changes for their children:
+ (aFrame && aContent->GetParent() &&
+ aFrame->PresContext()->FrameManager()->
+ GetDisplayContentsStyleFor(aContent->GetParent())),
+ "Shouldn't be trying to restyle non-elements directly, "
+ "except if it's a display:contents child");
+ MOZ_ASSERT(!(aHint & nsChangeHint_AllReflowHints) ||
+ (aHint & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ // Filter out all other changes for same content
+ if (!IsEmpty() && (aHint & nsChangeHint_ReconstructFrame)) {
+ if (aContent) {
+ // NOTE: This is captured by reference to please static analysis.
+ // Capturing it by value as a pointer should be fine in this case.
+ RemoveElementsBy([&](const nsStyleChangeData& aData) {
+ return aData.mContent == aContent;
+ });
+ }
+ }
+
+ if (!IsEmpty() && aFrame && aFrame == LastElement().mFrame) {
+ LastElement().mHint |= aHint;
+ return;
+ }
+
+ AppendElement(nsStyleChangeData { aFrame, aContent, aHint });
+}
diff --git a/layout/base/nsStyleChangeList.h b/layout/base/nsStyleChangeList.h
new file mode 100644
index 000000000..c5d12c73e
--- /dev/null
+++ b/layout/base/nsStyleChangeList.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a list of the recomputation that needs to be done in response to a
+ * style change
+ */
+
+#ifndef nsStyleChangeList_h___
+#define nsStyleChangeList_h___
+
+#include "mozilla/Attributes.h"
+
+#include "nsChangeHint.h"
+#include "nsCOMPtr.h"
+
+class nsIFrame;
+class nsIContent;
+
+struct nsStyleChangeData
+{
+ nsIFrame* mFrame; // weak
+ nsCOMPtr<nsIContent> mContent;
+ nsChangeHint mHint;
+};
+
+class nsStyleChangeList : private AutoTArray<nsStyleChangeData, 10>
+{
+ typedef AutoTArray<nsStyleChangeData, 10> base_type;
+ nsStyleChangeList(const nsStyleChangeList&) = delete;
+
+public:
+ using base_type::begin;
+ using base_type::end;
+ using base_type::IsEmpty;
+ using base_type::Clear;
+
+ nsStyleChangeList() { MOZ_COUNT_CTOR(nsStyleChangeList); }
+ ~nsStyleChangeList() { MOZ_COUNT_DTOR(nsStyleChangeList); }
+ void AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChangeHint aHint);
+};
+
+#endif /* nsStyleChangeList_h___ */
diff --git a/layout/base/nsStyleSheetService.cpp b/layout/base/nsStyleSheetService.cpp
new file mode 100644
index 000000000..676e46a50
--- /dev/null
+++ b/layout/base/nsStyleSheetService.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of interface for managing user and user-agent style sheets */
+
+#include "nsStyleSheetService.h"
+#include "mozilla/CSSStyleSheet.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Unused.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsICategoryManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISimpleEnumerator.h"
+#include "nsNetUtil.h"
+#include "nsIConsoleService.h"
+#include "nsIObserverService.h"
+#include "nsLayoutStatics.h"
+
+using namespace mozilla;
+
+nsStyleSheetService *nsStyleSheetService::gInstance = nullptr;
+
+nsStyleSheetService::nsStyleSheetService()
+{
+ static_assert(0 == AGENT_SHEET && 1 == USER_SHEET && 2 == AUTHOR_SHEET,
+ "Convention for Style Sheet");
+ NS_ASSERTION(!gInstance, "Someone is using CreateInstance instead of GetService");
+ gInstance = this;
+ nsLayoutStatics::AddRef();
+}
+
+nsStyleSheetService::~nsStyleSheetService()
+{
+ UnregisterWeakMemoryReporter(this);
+
+ gInstance = nullptr;
+ nsLayoutStatics::Release();
+}
+
+NS_IMPL_ISUPPORTS(
+ nsStyleSheetService, nsIStyleSheetService, nsIMemoryReporter)
+
+void
+nsStyleSheetService::RegisterFromEnumerator(nsICategoryManager *aManager,
+ const char *aCategory,
+ nsISimpleEnumerator *aEnumerator,
+ uint32_t aSheetType)
+{
+ if (!aEnumerator)
+ return;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(aEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> element;
+ if (NS_FAILED(aEnumerator->GetNext(getter_AddRefs(element))))
+ break;
+
+ nsCOMPtr<nsISupportsCString> icStr = do_QueryInterface(element);
+ NS_ASSERTION(icStr,
+ "category manager entries must be nsISupportsCStrings");
+
+ nsAutoCString name;
+ icStr->GetData(name);
+
+ nsXPIDLCString spec;
+ aManager->GetCategoryEntry(aCategory, name.get(), getter_Copies(spec));
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), spec);
+ if (uri)
+ LoadAndRegisterSheetInternal(uri, aSheetType);
+ }
+}
+
+int32_t
+nsStyleSheetService::FindSheetByURI(const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ nsIURI* aSheetURI)
+{
+ for (int32_t i = aSheets.Length() - 1; i >= 0; i-- ) {
+ bool bEqual;
+ nsIURI* uri = aSheets[i]->GetSheetURI();
+ if (uri
+ && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual))
+ && bEqual) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+nsresult
+nsStyleSheetService::Init()
+{
+ // If you make changes here, consider whether
+ // SVGDocument::EnsureNonSVGUserAgentStyleSheetsLoaded should be updated too.
+
+ // Child processes get their style sheets from the ContentParent.
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+
+ // Enumerate all of the style sheet URIs registered in the category
+ // manager and load them.
+
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+
+ NS_ENSURE_TRUE(catMan, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsISimpleEnumerator> sheets;
+ catMan->EnumerateCategory("agent-style-sheets", getter_AddRefs(sheets));
+ RegisterFromEnumerator(catMan, "agent-style-sheets", sheets, AGENT_SHEET);
+
+ catMan->EnumerateCategory("user-style-sheets", getter_AddRefs(sheets));
+ RegisterFromEnumerator(catMan, "user-style-sheets", sheets, USER_SHEET);
+
+ catMan->EnumerateCategory("author-style-sheets", getter_AddRefs(sheets));
+ RegisterFromEnumerator(catMan, "author-style-sheets", sheets, AUTHOR_SHEET);
+
+ RegisterWeakMemoryReporter(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::LoadAndRegisterSheet(nsIURI *aSheetURI,
+ uint32_t aSheetType)
+{
+ // Warn developers if their stylesheet URL has a #ref at the end.
+ // Stylesheet URIs don't benefit from having a #ref suffix -- and if the
+ // sheet is a data URI, someone might've created this #ref by accident (and
+ // truncated their data-URI stylesheet) by using an unescaped # character in
+ // a #RRGGBB color or #foo() ID-selector in their data-URI representation.
+ bool hasRef;
+ nsresult rv = aSheetURI->GetHasRef(&hasRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aSheetURI && hasRef) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
+ if (consoleService) {
+ const char16_t* message = u"nsStyleSheetService::LoadAndRegisterSheet: "
+ u"URI contains unescaped hash character, which might be truncating "
+ u"the sheet, if it's a data URI.";
+ consoleService->LogStringMessage(message);
+ }
+ }
+
+ rv = LoadAndRegisterSheetInternal(aSheetURI, aSheetType);
+ if (NS_SUCCEEDED(rv)) {
+ const char* message;
+ switch (aSheetType) {
+ case AGENT_SHEET:
+ message = "agent-sheet-added";
+ break;
+ case USER_SHEET:
+ message = "user-sheet-added";
+ break;
+ case AUTHOR_SHEET:
+ message = "author-sheet-added";
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ // We're guaranteed that the new sheet is the last sheet in
+ // mSheets[aSheetType]
+
+ // XXXheycam Once the nsStyleSheetService can hold ServoStyleSheets too,
+ // we'll need to include them in the notification.
+ StyleSheet* sheet = mSheets[aSheetType].LastElement();
+ if (sheet->IsGecko()) {
+ CSSStyleSheet* cssSheet = sheet->AsGecko();
+ serv->NotifyObservers(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, cssSheet),
+ message, nullptr);
+ } else {
+ NS_ERROR("stylo: can't notify observers of ServoStyleSheets");
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsTArray<dom::ContentParent*> children;
+ dom::ContentParent::GetAll(children);
+
+ if (children.IsEmpty()) {
+ return rv;
+ }
+
+ mozilla::ipc::URIParams uri;
+ SerializeURI(aSheetURI, uri);
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendLoadAndRegisterSheet(uri, aSheetType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsStyleSheetService::LoadAndRegisterSheetInternal(nsIURI *aSheetURI,
+ uint32_t aSheetType)
+{
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ css::SheetParsingMode parsingMode;
+ switch (aSheetType) {
+ case AGENT_SHEET:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case USER_SHEET:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case AUTHOR_SHEET:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ NS_WARNING("invalid sheet type argument");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // XXXheycam We'll need to load and register both a Gecko- and Servo-backed
+ // style sheet.
+ RefPtr<css::Loader> loader = new css::Loader(StyleBackendType::Gecko);
+
+ RefPtr<StyleSheet> sheet;
+ nsresult rv = loader->LoadSheetSync(aSheetURI, parsingMode, true, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSheets[aSheetType].AppendElement(sheet);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::SheetRegistered(nsIURI *sheetURI,
+ uint32_t aSheetType, bool *_retval)
+{
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET ||
+ aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+ NS_ENSURE_ARG_POINTER(sheetURI);
+ NS_PRECONDITION(_retval, "Null out param");
+
+ *_retval = (FindSheetByURI(mSheets[aSheetType], sheetURI) >= 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::PreloadSheet(nsIURI *aSheetURI, uint32_t aSheetType,
+ nsIDOMStyleSheet **aSheet)
+{
+ NS_PRECONDITION(aSheet, "Null out param");
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+ css::SheetParsingMode parsingMode;
+ switch (aSheetType) {
+ case AGENT_SHEET:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case USER_SHEET:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case AUTHOR_SHEET:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ NS_WARNING("invalid sheet type argument");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // XXXheycam PreloadSheet can't support ServoStyleSheets until they implement
+ // nsIDOMStyleSheet.
+
+ RefPtr<css::Loader> loader = new css::Loader(StyleBackendType::Gecko);
+
+ RefPtr<StyleSheet> sheet;
+ nsresult rv = loader->LoadSheetSync(aSheetURI, parsingMode, true, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(sheet->IsGecko(),
+ "stylo: didn't expect Loader to create a ServoStyleSheet");
+
+ RefPtr<CSSStyleSheet> cssSheet = sheet->AsGecko();
+ cssSheet.forget(aSheet);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::UnregisterSheet(nsIURI *aSheetURI, uint32_t aSheetType)
+{
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET ||
+ aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ int32_t foundIndex = FindSheetByURI(mSheets[aSheetType], aSheetURI);
+ NS_ENSURE_TRUE(foundIndex >= 0, NS_ERROR_INVALID_ARG);
+ RefPtr<StyleSheet> sheet = mSheets[aSheetType][foundIndex];
+ mSheets[aSheetType].RemoveElementAt(foundIndex);
+
+ const char* message;
+ switch (aSheetType) {
+ case AGENT_SHEET:
+ message = "agent-sheet-removed";
+ break;
+ case USER_SHEET:
+ message = "user-sheet-removed";
+ break;
+ case AUTHOR_SHEET:
+ message = "author-sheet-removed";
+ break;
+ }
+
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ // XXXheycam Once the nsStyleSheetService can hold ServoStyleSheets too,
+ // we'll need to include them in the notification.
+ if (sheet->IsGecko()) {
+ CSSStyleSheet* cssSheet = sheet->AsGecko();
+ serv->NotifyObservers(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, cssSheet),
+ message, nullptr);
+ } else {
+ NS_ERROR("stylo: can't notify observers of ServoStyleSheets");
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsTArray<dom::ContentParent*> children;
+ dom::ContentParent::GetAll(children);
+
+ if (children.IsEmpty()) {
+ return NS_OK;
+ }
+
+ mozilla::ipc::URIParams uri;
+ SerializeURI(aSheetURI, uri);
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendUnregisterSheet(uri, aSheetType);
+ }
+ }
+
+ return NS_OK;
+}
+
+//static
+nsStyleSheetService *
+nsStyleSheetService::GetInstance()
+{
+ static bool first = true;
+ if (first) {
+ // make sure at first call that it's inited
+ nsCOMPtr<nsIStyleSheetService> dummy =
+ do_GetService(NS_STYLESHEETSERVICE_CONTRACTID);
+ first = false;
+ }
+
+ return gInstance;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(StyleSheetServiceMallocSizeOf)
+
+NS_IMETHODIMP
+nsStyleSheetService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/style-sheet-service", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(StyleSheetServiceMallocSizeOf),
+ "Memory used for style sheets held by the style sheet service.");
+
+ return NS_OK;
+}
+
+size_t
+nsStyleSheetService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ for (auto& sheetArray : mSheets) {
+ n += sheetArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (StyleSheet* sheet : sheetArray) {
+ n += sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ return n;
+}
diff --git a/layout/base/nsStyleSheetService.h b/layout/base/nsStyleSheetService.h
new file mode 100644
index 000000000..982e1a05a
--- /dev/null
+++ b/layout/base/nsStyleSheetService.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implementation of interface for managing user and user-agent style sheets */
+
+#ifndef nsStyleSheetService_h_
+#define nsStyleSheetService_h_
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIMemoryReporter.h"
+#include "nsIStyleSheetService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSheet.h"
+
+class nsICategoryManager;
+class nsIMemoryReporter;
+class nsISimpleEnumerator;
+
+#define NS_STYLESHEETSERVICE_CID \
+{ 0x3b55e72e, 0xab7e, 0x431b, \
+ { 0x89, 0xc0, 0x3b, 0x06, 0xa8, 0xb1, 0x40, 0x16 } }
+
+#define NS_STYLESHEETSERVICE_CONTRACTID \
+ "@mozilla.org/content/style-sheet-service;1"
+
+class nsStyleSheetService final
+ : public nsIStyleSheetService
+ , public nsIMemoryReporter
+{
+ public:
+ nsStyleSheetService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTYLESHEETSERVICE
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsresult Init();
+
+ nsTArray<RefPtr<mozilla::StyleSheet>>* AgentStyleSheets()
+ {
+ return &mSheets[AGENT_SHEET];
+ }
+ nsTArray<RefPtr<mozilla::StyleSheet>>* UserStyleSheets()
+ {
+ return &mSheets[USER_SHEET];
+ }
+ nsTArray<RefPtr<mozilla::StyleSheet>>* AuthorStyleSheets()
+ {
+ return &mSheets[AUTHOR_SHEET];
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ static nsStyleSheetService *GetInstance();
+ static nsStyleSheetService *gInstance;
+
+ private:
+ ~nsStyleSheetService();
+
+ void RegisterFromEnumerator(nsICategoryManager *aManager,
+ const char *aCategory,
+ nsISimpleEnumerator *aEnumerator,
+ uint32_t aSheetType);
+
+ int32_t FindSheetByURI(const nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets,
+ nsIURI* aSheetURI);
+
+ // Like LoadAndRegisterSheet, but doesn't notify. If successful, the
+ // new sheet will be the last sheet in mSheets[aSheetType].
+ nsresult LoadAndRegisterSheetInternal(nsIURI *aSheetURI,
+ uint32_t aSheetType);
+
+ nsTArray<RefPtr<mozilla::StyleSheet>> mSheets[3];
+};
+
+#endif
diff --git a/layout/base/tests/Ahem.ttf b/layout/base/tests/Ahem.ttf
new file mode 100644
index 000000000..ac81cb031
--- /dev/null
+++ b/layout/base/tests/Ahem.ttf
Binary files differ
diff --git a/layout/base/tests/border_radius_hit_testing_iframe.html b/layout/base/tests/border_radius_hit_testing_iframe.html
new file mode 100644
index 000000000..a0f7ba1b9
--- /dev/null
+++ b/layout/base/tests/border_radius_hit_testing_iframe.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<title>border-radius hit testing</title>
+<style>
+
+ body { margin: 0; }
+
+ #one, #two {
+ border-radius: 100px 60px 40px 120px / 40px 60px 60px 80px;
+ margin-bottom: 100px;
+ }
+
+ #two { overflow: hidden }
+
+ #one, #two > div {
+ height: 200px;
+ background: blue;
+ cursor: progress;
+ }
+
+ #one:hover, #two > div:hover {
+ background: fuchsia;
+ }
+
+</style>
+<body>
+<div id="one"></div>
+<div id="two"><div></div></div>
diff --git a/layout/base/tests/browser.ini b/layout/base/tests/browser.ini
new file mode 100644
index 000000000..54b78b92f
--- /dev/null
+++ b/layout/base/tests/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+[browser_bug617076.js]
+[browser_disableDialogs_onbeforeunload.js]
+[browser_onbeforeunload_only_after_interaction.js]
+[browser_onbeforeunload_only_after_interaction_in_frame.js]
diff --git a/layout/base/tests/browser_bug617076.js b/layout/base/tests/browser_bug617076.js
new file mode 100644
index 000000000..d4e55f9b8
--- /dev/null
+++ b/layout/base/tests/browser_bug617076.js
@@ -0,0 +1,46 @@
+/**
+ * 1. load about:addons in a new tab and select that tab
+ * 2. insert a button with tooltiptext
+ * 3. create a new blank tab and select that tab
+ * 4. select the about:addons tab and hover the inserted button
+ * 5. remove the about:addons tab
+ * 6. remove the blank tab
+ *
+ * the test succeeds if it doesn't trigger any assertions
+ */
+
+add_task(function* test() {
+ // Open the test tab
+ let testTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:addons");
+
+ // insert button into test page content
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let e = doc.createElement("button");
+ e.setAttribute("label", "hello");
+ e.setAttribute("tooltiptext", "world");
+ e.setAttribute("id", "test-button");
+ doc.documentElement.insertBefore(e, doc.documentElement.firstChild);
+ });
+
+ // open a second tab and select it
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true);
+ gBrowser.selectedTab = tab2;
+
+ // Select the testTab then perform mouse events on inserted button
+ gBrowser.selectedTab = testTab;
+ let browser = gBrowser.selectedBrowser;
+ EventUtils.disableNonTestMouseEvents(true);
+ try {
+ yield BrowserTestUtils.synthesizeMouse("#test-button", 1, 1, { type: "mouseover" }, browser);
+ yield BrowserTestUtils.synthesizeMouse("#test-button", 2, 6, { type: "mousemove" }, browser);
+ yield BrowserTestUtils.synthesizeMouse("#test-button", 2, 4, { type: "mousemove" }, browser);
+ } finally {
+ EventUtils.disableNonTestMouseEvents(false);
+ }
+
+ // cleanup
+ yield BrowserTestUtils.removeTab(testTab);
+ yield BrowserTestUtils.removeTab(tab2);
+ ok(true, "pass if no assertions");
+});
diff --git a/layout/base/tests/browser_disableDialogs_onbeforeunload.js b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
new file mode 100644
index 000000000..c9385f670
--- /dev/null
+++ b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
@@ -0,0 +1,56 @@
+function pageScript() {
+ window.addEventListener("beforeunload", function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ }, true);
+}
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+const PAGE_URL =
+ "data:text/html," + encodeURIComponent("<script>(" + pageScript.toSource() + ")();</script>");
+
+add_task(function* enableDialogs() {
+ // The onbeforeunload dialog should appear.
+ let dialogShown = false;
+ function onDialogShown(node) {
+ dialogShown = true;
+ let dismissButton = node.ui.button0;
+ dismissButton.click();
+ }
+ let obsName = "tabmodal-dialog-loaded";
+ Services.obs.addObserver(onDialogShown, obsName, false);
+ yield openPage(true);
+ Services.obs.removeObserver(onDialogShown, obsName);
+ Assert.ok(dialogShown);
+});
+
+add_task(function* disableDialogs() {
+ // The onbeforeunload dialog should NOT appear.
+ yield openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+function* openPage(enableDialogs) {
+ // Open about:blank in a new tab.
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ // Load the content script in the frame.
+ let methodName = enableDialogs ? "enableDialogs" : "disableDialogs";
+ yield ContentTask.spawn(browser, methodName, function* (name) {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.obs.addObserver(doc => {
+ if (content && doc == content.document) {
+ content.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils)[name]();
+ }
+ }, "document-element-inserted", false);
+ });
+ // Load the page.
+ yield BrowserTestUtils.loadURI(browser, PAGE_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+ // And then navigate away.
+ yield BrowserTestUtils.loadURI(browser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(browser);
+ });
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
new file mode 100644
index 000000000..d079bcb38
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
@@ -0,0 +1,53 @@
+function pageScript() {
+ window.addEventListener("beforeunload", function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ }, true);
+}
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", true]]});
+
+const PAGE_URL =
+ "data:text/html," + encodeURIComponent("<script>(" + pageScript.toSource() + ")();</script>");
+
+add_task(function* doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogShown = false;
+ function onDialogShown(node) {
+ dialogShown = true;
+ let dismissButton = node.ui.button0;
+ dismissButton.click();
+ }
+ let obsName = "tabmodal-dialog-loaded";
+ Services.obs.addObserver(onDialogShown, obsName, false);
+ yield* openPage(true);
+ Services.obs.removeObserver(onDialogShown, obsName);
+ Assert.ok(dialogShown, "Should have shown dialog.");
+});
+
+add_task(function* noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ yield openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+function* openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ // Load the page.
+ yield BrowserTestUtils.loadURI(browser, PAGE_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ if (shouldClick) {
+ yield BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+ }
+ let hasInteractedWith = yield ContentTask.spawn(browser, "", function() {
+ return content.document.userHasInteracted;
+ });
+ is(shouldClick, hasInteractedWith, "Click should update document interactivity state");
+ // And then navigate away.
+ yield BrowserTestUtils.loadURI(browser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(browser);
+ });
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
new file mode 100644
index 000000000..34dbf2308
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
@@ -0,0 +1,59 @@
+function pageScript() {
+ window.addEventListener("beforeunload", function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ }, true);
+}
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", true]]});
+
+const FRAME_URL =
+ "data:text/html," + encodeURIComponent("<body>Just a frame</body>");
+
+const PAGE_URL =
+ "data:text/html," + encodeURIComponent("<iframe src='" + FRAME_URL + "'></iframe><script>(" + pageScript.toSource() + ")();</script>");
+
+add_task(function* doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogShown = false;
+ function onDialogShown(node) {
+ dialogShown = true;
+ let dismissButton = node.ui.button0;
+ dismissButton.click();
+ }
+ let obsName = "tabmodal-dialog-loaded";
+ Services.obs.addObserver(onDialogShown, obsName, false);
+ yield* openPage(true);
+ Services.obs.removeObserver(onDialogShown, obsName);
+ Assert.ok(dialogShown, "Should have shown dialog.");
+});
+
+add_task(function* noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ yield openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+function* openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ // Load the page.
+ yield BrowserTestUtils.loadURI(browser, PAGE_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ if (shouldClick) {
+ yield BrowserTestUtils.synthesizeMouse(function() {
+ return content.frames[0].document.body;
+ }, 2, 2, {}, browser);
+ }
+ let hasInteractedWith = yield ContentTask.spawn(browser, "", function() {
+ return [content.document.userHasInteracted, content.frames[0].document.userHasInteracted];
+ });
+ is(shouldClick, hasInteractedWith[0], "Click should update parent interactivity state");
+ is(shouldClick, hasInteractedWith[1], "Click should update frame interactivity state");
+ // And then navigate away.
+ yield BrowserTestUtils.loadURI(browser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(browser);
+ });
+}
diff --git a/layout/base/tests/bug1007065-1-ref.html b/layout/base/tests/bug1007065-1-ref.html
new file mode 100644
index 000000000..fe867dfa0
--- /dev/null
+++ b/layout/base/tests/bug1007065-1-ref.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <input type="text" style="text-align:right; border-width:0;">
+ <script>
+ function start() {
+ var input = document.querySelector("input");
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1007065-1.html b/layout/base/tests/bug1007065-1.html
new file mode 100644
index 000000000..174ffb888
--- /dev/null
+++ b/layout/base/tests/bug1007065-1.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <input type="text" style="text-align:right; overflow:hidden; border-width:0;">
+ <script>
+ function start() {
+ var input = document.querySelector("input");
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1007067-1-ref.html b/layout/base/tests/bug1007067-1-ref.html
new file mode 100644
index 000000000..b303167dd
--- /dev/null
+++ b/layout/base/tests/bug1007067-1-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+</head>
+<body onload="start()">
+<textarea style="width:500px; height: 200px; border-width:0">-----------------------------------------
+{insert newline before opening bracket}-----------------------------------b</textarea>
+<select><option value="AED">AED - United Arab Emirates Dirham - د.إ</option></select>
+ <script>
+ function start() {
+ var input = document.querySelector("textarea");
+ input.selectionStart = 42
+ input.selectionEnd = 42
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1007067-1.html b/layout/base/tests/bug1007067-1.html
new file mode 100644
index 000000000..6284d13ef
--- /dev/null
+++ b/layout/base/tests/bug1007067-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+</head>
+<body onload="start()">
+<textarea style="width:500px; height: 200px; border-width:0">-----------------------------------------{insert newline before opening bracket}-----------------------------------b</textarea>
+<select><option value="AED">AED - United Arab Emirates Dirham - د.إ</option></select>
+ <script>
+ function start() {
+ var input = document.querySelector("textarea");
+ input.selectionStart = 41
+ input.selectionEnd = 41
+ input.focus();
+ window.parent.synthesizeKey("VK_RETURN", { }, window)
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1061468-ref.html b/layout/base/tests/bug1061468-ref.html
new file mode 100644
index 000000000..9a3330c9f
--- /dev/null
+++ b/layout/base/tests/bug1061468-ref.html
@@ -0,0 +1,13 @@
+<html>
+<head></head>
+<body>
+
+<div id="firstDiv">
+Parent1
+</div>
+
+<div id="secondDiv">
+Parent2<div contenteditable id="editable">Testing 1</div></div>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug1061468.html b/layout/base/tests/bug1061468.html
new file mode 100644
index 000000000..720c0e610
--- /dev/null
+++ b/layout/base/tests/bug1061468.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+function runTest() {
+ var sel = window.getSelection();
+ var r = new Range()
+ r.setStart(document.querySelector("#firstDiv"),0);
+ r.setEnd(document.querySelector("#firstDiv"),1);
+ sel.addRange(r)
+
+ document.querySelector("#editable").focus();
+ document.querySelector("#secondDiv").appendChild(document.querySelector("#editable"));
+
+ is(sel.rangeCount, 1, "still have a range in Selection")
+ var s=""
+ try {
+ var r2 = sel.getRangeAt(0)
+ s+=r2.startContainer.tagName
+ s+=r2.startOffset
+ s+=r2.endContainer.tagName
+ s+=r2.endOffset
+ } catch(e) {}
+
+ is(s, "DIV1DIV1", "the range gravitated correctly")
+}
+</script>
+</head>
+<body onload="runTest()">
+
+<div id="firstDiv">
+Parent1
+<div contenteditable id="editable">Testing 1</div>
+</div>
+
+<div id="secondDiv">
+Parent2</div>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-1-ref.html b/layout/base/tests/bug106855-1-ref.html
new file mode 100644
index 000000000..2d3ceea40
--- /dev/null
+++ b/layout/base/tests/bug106855-1-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RIGHT'); // now after "A"
+ sendKey('RIGHT'); //
+ sendKey('RIGHT'); //
+ sendKey('RIGHT'); // now at the last line
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-1.html b/layout/base/tests/bug106855-1.html
new file mode 100644
index 000000000..6c0934211
--- /dev/null
+++ b/layout/base/tests/bug106855-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('DOWN'); // now after "A"
+ sendKey('DOWN'); //
+ sendKey('DOWN'); // now at the last line
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-2.html b/layout/base/tests/bug106855-2.html
new file mode 100644
index 000000000..34c50fafd
--- /dev/null
+++ b/layout/base/tests/bug106855-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('DOWN'); // now after "A"
+ sendKey('DOWN'); //
+ sendKey('DOWN'); //
+ sendKey('DOWN'); // now at the last line
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1078327_inner.html b/layout/base/tests/bug1078327_inner.html
new file mode 100644
index 000000000..9e32fc996
--- /dev/null
+++ b/layout/base/tests/bug1078327_inner.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1078327
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1078327</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #listener { background: yellow; padding: 10px; }
+ #mediator { background: red; padding: 20px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_capture = false;
+ var test_mediator_over = false;
+ var test_mediator_move = false;
+ var test_mediator_out = false;
+ var test_listener = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type + ". Mediator.setPointerCapture()");
+ mediator.setPointerCapture(event.pointerId);
+ test_target = true;
+ }
+ function MediatorHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ if(event.type == "gotpointercapture")
+ test_capture = true;
+ if(!test_capture)
+ return;
+ if(event.type == "pointermove")
+ test_mediator_move++;
+ if(event.type == "pointerover")
+ test_mediator_over++;
+ if(event.type == "pointerout")
+ test_mediator_out++;
+ if(event.type == "lostpointercapture")
+ test_capture = false;
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetHandler, false);
+ mediator.addEventListener("gotpointercapture", MediatorHandler, false);
+ mediator.addEventListener("pointerover", MediatorHandler, false);
+ mediator.addEventListener("pointermove", MediatorHandler, false);
+ mediator.addEventListener("pointerout", MediatorHandler, false);
+ mediator.addEventListener("lostpointercapture", MediatorHandler, false);
+ listener.addEventListener("pointermove", ListenerHandler, false);
+ var rect_t = target.getBoundingClientRect();
+ var rect_m = mediator.getBoundingClientRect();
+ var rect_l = listener.getBoundingClientRect();
+ synthesizePointer(target, rect_t.width/2, rect_t.height/20, {type: "pointerdown"});
+ synthesizePointer(target, rect_t.width/2, rect_t.height/20, {type: "pointermove"});
+ synthesizePointer(mediator, rect_m.width/2, rect_m.height/20, {type: "pointermove"});
+ synthesizePointer(listener, rect_l.width/2, rect_l.height/20, {type: "pointermove"});
+ synthesizePointer(mediator, rect_m.width/2, rect_m.height/20, {type: "pointermove"});
+ synthesizePointer(target, rect_t.width/2, rect_t.height/20, {type: "pointermove"});
+ synthesizePointer(target, rect_t.width/2, rect_t.height/20, {type: "pointerup"});
+ synthesizePointer(target, rect_t.width/2, rect_t.height/20, {type: "pointermove"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "pointerdown event should be received by target");
+ parent.is(test_capture, false, "test_capture should be false at the end of the test");
+ parent.is(test_mediator_over, 1, "mediator should receive pointerover event only once");
+ parent.is(test_mediator_move, 5, "mediator should receive pointermove event five times");
+ parent.is(test_mediator_out, 1, "mediator should receive pointerout event only once");
+ parent.is(test_listener, false, "listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1078327">Mozilla Bug 1078327</a>
+ <div id="mediator">
+ <div id="listener">div id=listener</div>
+ </div>
+ <div id="target">div id=target</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1080360_inner.html b/layout/base/tests/bug1080360_inner.html
new file mode 100644
index 000000000..95862f9c6
--- /dev/null
+++ b/layout/base/tests/bug1080360_inner.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080360
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080360</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #listener { background: yellow; padding: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_listener_got = false;
+ var test_listener_lost = false;
+ var test_document = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type);
+ listener.setPointerCapture(event.pointerId);
+ test_target = true;
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ if(event.type == "gotpointercapture") {
+ test_listener_got = true;
+ listener.parentNode.removeChild(listener);
+ }
+ if(event.type == "lostpointercapture")
+ test_listener_lost = true;
+ }
+ function DocumentHandler(event) {
+ logger("Document receive event: " + event.type);
+ if(event.type == "lostpointercapture")
+ test_document = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerHandler, false);
+ document.addEventListener("lostpointercapture", DocumentHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "pointerdown event should be received by target");
+ parent.is(test_listener_got, true, "gotpointercapture event should be received by listener");
+ parent.is(test_listener_lost, false, "listener should not receive lostpointercapture event");
+ parent.is(test_document, true, "document should receive lostpointercapture event");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080360">Mozilla Bug 1080360</a>
+ <div id="target">div id=target</div>
+ <div id="listener">div id=listener</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1080361_inner.html b/layout/base/tests/bug1080361_inner.html
new file mode 100644
index 000000000..be2d8766f
--- /dev/null
+++ b/layout/base/tests/bug1080361_inner.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080361
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080361</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #mediator, #listener { background: yellow; margin: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_target_down = false;
+ var test_target_up = false;
+ var test_first_exc = false;
+ var test_second_exc = false;
+ var test_third_exc = false;
+ var test_fourth_exc = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ test_target_down = true;
+ try {
+ logger("target.setPointerCapture()");
+ target.setPointerCapture(31415);
+ } catch(exc) {
+ test_first_exc = true;
+ parent.is(exc.name, "InvalidPointerId", "Exception InvalidPointerId should be fired");
+ }
+ try {
+ logger("mediator.setPointerCapture()");
+ mediator.parentNode.removeChild(mediator);
+ mediator.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_second_exc = true;
+ parent.is(exc.name, "InvalidStateError", "Exception InvalidStateError should be fired");
+ }
+ try {
+ logger("listener.setPointerCapture()");
+ listener.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_third_exc = true;
+ }
+ }
+ function TargetUpHandler(event) {
+ logger("Target receive event: " + event.type);
+ test_target_up = true;
+ try {
+ logger("target.setPointerCapture()");
+ target.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_fourth_exc = true;
+ }
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ test_listener = true;
+ listener.releasePointerCapture(event.pointerId);
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ target.addEventListener("pointerup", TargetUpHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ setTimeout(function() {
+ parent.is(test_target_down, true, "pointerdown event should be received by target");
+ parent.is(test_target_up, true, "pointerup event should be received by target");
+ parent.is(test_first_exc, true, "first exception should be thrown");
+ parent.is(test_second_exc, true, "second exception should be thrown");
+ parent.is(test_third_exc, false, "third exception should not be thrown");
+ parent.is(test_fourth_exc, false, "fourth exception should not be thrown");
+ parent.is(test_listener, true, "listener should receive gotpointercapture event");
+ logger("finishTest");
+ parent.finishTest();
+ }, 1000);
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080361">Mozilla Bug 1080361</a>
+ <div id="target">div id=target</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="listener">div id=listener</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1082486-1-ref.html b/layout/base/tests/bug1082486-1-ref.html
new file mode 100644
index 000000000..043779cb9
--- /dev/null
+++ b/layout/base/tests/bug1082486-1-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ /* Eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+ <input id='i' value="abcdefghd" style="text-indent: -10px">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-1.html b/layout/base/tests/bug1082486-1.html
new file mode 100644
index 000000000..de6801f39
--- /dev/null
+++ b/layout/base/tests/bug1082486-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ /* Eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ </head>
+ <body onload="focusInput();">
+ <script>
+ function focusInput() {
+ var inp = document.getElementById('i');
+ inp.focus();
+ }
+ </script>
+
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The caret will not be seen when the input is focused. -->
+ <input id='i' value="abcdefghd" style="text-indent: -10px">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-2-ref.html b/layout/base/tests/bug1082486-2-ref.html
new file mode 100644
index 000000000..5b61a54b6
--- /dev/null
+++ b/layout/base/tests/bug1082486-2-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The whole input will not be seen. -->
+ <input id='i' value="abcdefghd" style="position: absolute; top: -100px; left: 0px;">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-2.html b/layout/base/tests/bug1082486-2.html
new file mode 100644
index 000000000..d367883eb
--- /dev/null
+++ b/layout/base/tests/bug1082486-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body onload="document.getElementById('i').focus();">
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The whole input will not be seen. -->
+ <input id='i' value="abcdefghd" style="position: absolute; top: -100px; left: 0px;">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1093686_inner.html b/layout/base/tests/bug1093686_inner.html
new file mode 100644
index 000000000..527673108
--- /dev/null
+++ b/layout/base/tests/bug1093686_inner.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<head>
+ <title>Testing effect of listener on body</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .target { position:absolute; left:200px; top:200px; width:200px; height:200px; background:blue; }
+ </style>
+</head>
+<body id="body" onload="setTimeout(runTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
+<div id="content">
+ <div id="ruler" style="position:absolute; left:0; top:0; width:1mozmm; height:0;"></div>
+ <div class="target" id="t"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+var eventTarget;
+window.onmousedown = function(event) { eventTarget = event.target; };
+
+// Make sure the target div is "clickable" by adding a click listener on it.
+document.getElementById('t').addEventListener('click', function(e) {
+ parent.ok(true, "target was clicked on");
+}, false);
+
+// Helper functions
+
+function testMouseClick(aX, aY, aExpectedId, aMsg) {
+ eventTarget = null;
+ synthesizeMouseAtPoint(aX, aY, {});
+ try {
+ parent.is(eventTarget.id, aExpectedId,
+ "checking offset " + aX + "," + aY + " hit " + aExpectedId + " [" + aMsg + "]");
+ } catch (ex) {
+ parent.ok(false, "checking offset " + aX + "," + aY + " hit " + aExpectedId + " [" + aMsg + "]; got " + eventTarget);
+ }
+}
+
+function testWithAndWithoutBodyListener(aX, aY, aExpectedId, aMsg) {
+ var func = function(e) {
+ // no-op function
+ parent.ok(true, "body was clicked on");
+ };
+ testMouseClick(aX, aY, aExpectedId, aMsg + " without listener on body");
+ document.body.addEventListener("click", func, false);
+ testMouseClick(aX, aY, aExpectedId, aMsg + " with listener on body");
+ document.body.removeEventListener("click", func, false);
+}
+
+// Main tests
+
+var mm;
+function runTest() {
+ mm = document.getElementById("ruler").getBoundingClientRect().width;
+ parent.ok(4*mm >= 10, "WARNING: mm " + mm + " too small in this configuration. Test results will be bogus");
+
+ // Test near the target, check it hits the target
+ testWithAndWithoutBodyListener(200 - 2*mm, 200 - 2*mm, "t", "basic click retargeting");
+ // Test on the target, check it hits the target
+ testWithAndWithoutBodyListener(200 + 2*mm, 200 + 2*mm, "t", "direct click");
+ // Test outside the target, check it hits the root
+ testWithAndWithoutBodyListener(40, 40, "body", "click way outside target");
+
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.enabled", false]]}, runTest2);
+}
+
+function runTest2() {
+ // In this test, mouse event retargeting is disabled.
+
+ // Test near the target, check it hits the body
+ testWithAndWithoutBodyListener(200 - 2*mm, 200 - 2*mm, "body", "basic click retargeting");
+ // Test on the target, check it hits the target
+ testWithAndWithoutBodyListener(200 + 2*mm, 200 + 2*mm, "t", "direct click");
+ // Test outside the target, check it hits the root
+ testWithAndWithoutBodyListener(40, 40, "body", "click way outside target");
+
+ parent.finishTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1097242-1-ref.html b/layout/base/tests/bug1097242-1-ref.html
new file mode 100644
index 000000000..dd62f18ee
--- /dev/null
+++ b/layout/base/tests/bug1097242-1-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function test() {
+ focus();
+ var div = document.querySelector("div");
+ div.focus();
+ getSelection().collapse(div.firstChild, 0);
+ }
+ </script>
+ <body onload="test()">
+ <div contenteditable spellcheck="false" style="outline: none">foo<span>bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1097242-1.html b/layout/base/tests/bug1097242-1.html
new file mode 100644
index 000000000..dc5d55eb1
--- /dev/null
+++ b/layout/base/tests/bug1097242-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ focus();
+ synthesizeMouseAtCenter(document.querySelector("span"), {});
+ }
+ function focused() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body onload="setTimeout(test, 0)">
+ <div contenteditable spellcheck="false" onfocus="focused()"
+ style="outline: none">foo<span contenteditable=false
+ style="-moz-user-select: none">bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-1-ref.html b/layout/base/tests/bug1109968-1-ref.html
new file mode 100644
index 000000000..28bcf608a
--- /dev/null
+++ b/layout/base/tests/bug1109968-1-ref.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><a href="#">bar</a></div>baz</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "baz"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-1.html b/layout/base/tests/bug1109968-1.html
new file mode 100644
index 000000000..a8d0d216f
--- /dev/null
+++ b/layout/base/tests/bug1109968-1.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><a href="#">bar</a></div>baz</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "baz"
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-2-ref.html b/layout/base/tests/bug1109968-2-ref.html
new file mode 100644
index 000000000..9638c35b3
--- /dev/null
+++ b/layout/base/tests/bug1109968-2-ref.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><img src="image_rgrg-256x256.png" width="10" height="10"></div>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-2.html b/layout/base/tests/bug1109968-2.html
new file mode 100644
index 000000000..159311ad3
--- /dev/null
+++ b/layout/base/tests/bug1109968-2.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><img src="image_rgrg-256x256.png" width="10" height="10"></div>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 6; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1123067-1.html b/layout/base/tests/bug1123067-1.html
new file mode 100644
index 000000000..86c8357ff
--- /dev/null
+++ b/layout/base/tests/bug1123067-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1123067</title>
+ <script>
+ function click(id) {
+ var e = document.querySelector(id);
+ synthesizeMouse(e, 1, 1, {type: "mousedown"}, window);
+ synthesizeMouse(e, 1, 1, {type: "mouseup"}, window);
+ }
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ synthesizeKey("VK_LEFT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ click('#test1')
+ }
+ </script>
+ <style>
+ div { -moz-user-select:none; }
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="test()">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-2.html b/layout/base/tests/bug1123067-2.html
new file mode 100644
index 000000000..0139e7ac7
--- /dev/null
+++ b/layout/base/tests/bug1123067-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ synthesizeKey("VK_LEFT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var e = document.querySelector('#test1');
+ e.focus();
+ }
+ </script>
+ <style>
+ div { -moz-user-select:none; }
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="setTimeout(test,0)">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-3.html b/layout/base/tests/bug1123067-3.html
new file mode 100644
index 000000000..71c6bf1a3
--- /dev/null
+++ b/layout/base/tests/bug1123067-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait" style="-moz-user-select:none" spellcheck="false">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ synthesizeKey("VK_LEFT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.designMode='on';
+ document.querySelector('div').focus();
+ document.body.offsetHeight;
+ setTimeout(test,0)
+ }
+ </script>
+ <style>
+ div { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div>This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-ref.html b/layout/base/tests/bug1123067-ref.html
new file mode 100644
index 000000000..5fe2a5981
--- /dev/null
+++ b/layout/base/tests/bug1123067-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ synthesizeKey("VK_LEFT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var e = document.querySelector('#test1');
+ e.focus();
+ }
+ </script>
+ <style>
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="test()">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1132768-1-ref.html b/layout/base/tests/bug1132768-1-ref.html
new file mode 100644
index 000000000..0fea14cf0
--- /dev/null
+++ b/layout/base/tests/bug1132768-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function test() {
+ focus();
+ getSelection().selectAllChildren(document.querySelector("span"));
+ }
+ </script>
+ <body onload="test()">
+ <div>foo<span>bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1132768-1.html b/layout/base/tests/bug1132768-1.html
new file mode 100644
index 000000000..dafa3aa3e
--- /dev/null
+++ b/layout/base/tests/bug1132768-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ focus();
+ synthesizeMouseAtCenter(document.querySelector("span"), {});
+ }
+ function focused() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body onload="setTimeout(test, 0)">
+ <div contenteditable spellcheck="false" onfocus="focused()"
+ style="outline: none">foo<span contenteditable=false>bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1153130_inner.html b/layout/base/tests/bug1153130_inner.html
new file mode 100644
index 000000000..0bb8c0cd7
--- /dev/null
+++ b/layout/base/tests/bug1153130_inner.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1153130</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target { background: yellow; padding: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var test_down = false;
+ var test_capture = false;
+ var test_move = false;
+ var test_success = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type);
+ if(event.type == "pointerdown") {
+ test_down = true;
+ target.setPointerCapture(event.pointerId);
+ } else if(event.type == "gotpointercapture") {
+ test_capture = true;
+ } else if(event.type == "pointermove" && test_capture) {
+ test_move = true;
+ }
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('target');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest() {
+ logger("executeTest");
+ target = document.getElementById("target");
+ target.addEventListener("pointerdown", TargetHandler, false);
+ target.addEventListener("gotpointercapture", TargetHandler, false);
+ target.addEventListener("pointermove", TargetHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/5, rect.height/5, {type: "pointermove"});
+ synthesizePointer(target, rect.width/5, rect.height/5, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/4, rect.height/4, {type: "pointermove"});
+ synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointermove"});
+ synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointerup"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ test_success = true;
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down, true, "pointerdown event should be received by target");
+ parent.is(test_capture, true, "gotpointercapture event should be received by target");
+ parent.is(test_move, true, "pointermove event should be received by target while pointer capture is active");
+ parent.is(test_success, true, "Firefox should be live!");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1153130">Mozilla Bug 1153130</a>
+ <div id="target">div id=target</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug1162990_inner_1.html b/layout/base/tests/bug1162990_inner_1.html
new file mode 100644
index 000000000..4ea5edb5c
--- /dev/null
+++ b/layout/base/tests/bug1162990_inner_1.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ div#basket {
+ background: red;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#target {
+ background: lightgreen;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#child {
+ background: lightblue;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#listener {
+ background: yellow;
+ padding: 10px;
+ margin: 10px;
+ }
+ </style>
+ <script type="application/javascript">
+ var basket;
+ var target;
+ var child;
+ var listener;
+
+ var test_basketLeave = 0;
+ var test_targetGotCapture = 0;
+ var test_targetLostCapture = 0;
+ var test_targetLeave = 0;
+ var test_childLeave = 0;
+ var test_listenerDown = 0;
+ var test_listenerLeave = 0;
+
+ function basketLeaveHandler(event) {
+ logger("basket: " + event.type);
+ test_basketLeave++;
+ }
+ function targetGotHandler(event) {
+ logger("target: " + event.type);
+ test_targetGotCapture++;
+ }
+ function targetLostHandler(event) {
+ logger("target: " + event.type);
+ test_targetLostCapture++;
+ }
+ function targetLeaveHandler(event) {
+ logger("target: " + event.type);
+ test_targetLeave++;
+ }
+ function childLeaveHandler(event) {
+ logger("child: " + event.type);
+ test_childLeave++;
+ }
+ function listenerDownHandler(event) {
+ logger("listener: " + event.type);
+ target.setPointerCapture(event.pointerId);
+ test_listenerDown++;
+ }
+ function listenerLeaveHandler(event) {
+ logger("listener: " + event.type);
+ test_listenerLeave++;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+
+ function setEventHandlers() {
+ basket = document.getElementById("basket");
+ target = document.getElementById("target");
+ child = document.getElementById("child");
+ listener = document.getElementById("listener");
+
+ basket.addEventListener("pointerleave", basketLeaveHandler, false);
+ target.addEventListener("gotpointercapture", targetGotHandler, false);
+ target.addEventListener("lostpointercapture", targetLostHandler, false);
+ target.addEventListener("pointerleave", targetLeaveHandler, false);
+ child.addEventListener("pointerleave", childLeaveHandler, false);
+ listener.addEventListener("pointerdown", listenerDownHandler, false);
+ listener.addEventListener("pointerleave", listenerLeaveHandler, false);
+ }
+
+ function executeTest()
+ {
+ logger("executeTest");
+ setEventHandlers();
+ var rectCd = child.getBoundingClientRect();
+ var rectLr = listener.getBoundingClientRect();
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/2, {type: "pointerdown"});
+ synthesizePointer(child, rectCd.width/3, rectCd.height/2, {type: "pointermove"});
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/2, {type: "pointermove"});
+ synthesizePointer(child, rectCd.width/3, rectCd.height/2, {type: "pointermove"});
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/2, {type: "pointermove"});
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/2, {type: "pointerup"});
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/3, {type: "pointermove"});
+ finishTest();
+ }
+
+ function finishTest() {
+ parent.is(test_basketLeave, 0, "Part1: basket should not receive pointerleave event after pointer capturing");
+ parent.is(test_targetGotCapture, 1, "Part1: target should receive gotpointercapture event");
+ parent.is(test_targetLostCapture, 1, "Part1: target should receive lostpointercapture event");
+ parent.is(test_targetLeave, 2, "Part1: target should receive pointerleave event two times");
+ parent.is(test_childLeave, 0, "Part1: child should not receive pointerleave event after pointer capturing");
+ parent.is(test_listenerDown, 1, "Part1: listener should receive pointerdown event");
+ parent.is(test_listenerLeave, 1, "Part1: listener should receive pointerleave event only one time");
+ logger("finishTest");
+ parent.finishTest();
+ }
+
+ function logger(message) {
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162990">Mozilla Bug 1162990 Test 1</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="basket">div id=basket
+ <div id="target">div id=target
+ <div id="child">div id=child</div>
+ </div>
+ </div>
+ <div id="listener">div id=listener</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1162990_inner_2.html b/layout/base/tests/bug1162990_inner_2.html
new file mode 100644
index 000000000..54aa74ca3
--- /dev/null
+++ b/layout/base/tests/bug1162990_inner_2.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ div#basket {
+ background: red;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#target {
+ background: lightgreen;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#child {
+ background: lightblue;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#listener {
+ background: yellow;
+ padding: 10px;
+ margin: 10px;
+ }
+ </style>
+ <script type="application/javascript">
+ var basket;
+ var target;
+ var child;
+ var listener;
+
+ var test_basketLeave = 0;
+ var test_targetDown = 0;
+ var test_targetGotCapture = 0;
+ var test_targetLostCapture = 0;
+ var test_targetLeave = 0;
+ var test_childLeave = 0;
+ var test_listenerLeave = 0;
+
+ function basketLeaveHandler(event) {
+ logger("basket: " + event.type);
+ test_basketLeave++;
+ }
+ function targetDownHandler(event) {
+ logger("target: " + event.type);
+ target.setPointerCapture(event.pointerId);
+ test_targetDown++;
+ }
+ function targetGotHandler(event) {
+ logger("target: " + event.type);
+ test_targetGotCapture++;
+ }
+ function targetLostHandler(event) {
+ logger("target: " + event.type);
+ test_targetLostCapture++;
+ }
+ function targetLeaveHandler(event) {
+ logger("target: " + event.type);
+ test_targetLeave++;
+ }
+ function childLeaveHandler(event) {
+ logger("child: " + event.type);
+ test_childLeave++;
+ }
+ function listenerLeaveHandler(event) {
+ logger("listener: " + event.type);
+ test_listenerLeave++;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+
+ function setEventHandlers() {
+ basket = document.getElementById("basket");
+ target = document.getElementById("target");
+ child = document.getElementById("child");
+ listener = document.getElementById("listener");
+
+ basket.addEventListener("pointerleave", basketLeaveHandler, false);
+ target.addEventListener("pointerdown", targetDownHandler, false);
+ target.addEventListener("gotpointercapture", targetGotHandler, false);
+ target.addEventListener("lostpointercapture", targetLostHandler, false);
+ target.addEventListener("pointerleave", targetLeaveHandler, false);
+ child.addEventListener("pointerleave", childLeaveHandler, false);
+ listener.addEventListener("pointerleave", listenerLeaveHandler, false);
+ }
+
+ function executeTest()
+ {
+ logger("executeTest");
+ setEventHandlers();
+ var rectTg = target.getBoundingClientRect();
+ var rectCd = child.getBoundingClientRect();
+ var rectLr = listener.getBoundingClientRect();
+ synthesizePointer(target, rectTg.width/3, rectTg.height/7, {type: "pointerdown"});
+ synthesizePointer(child, rectCd.width/3, rectCd.height/2, {type: "pointermove"});
+ synthesizePointer(listener, rectLr.width/3, rectLr.height/2, {type: "pointermove"});
+ synthesizePointer(child, rectCd.width/3, rectCd.height/2, {type: "pointermove"});
+ synthesizePointer(target, rectTg.width/3, rectTg.height/7, {type: "pointermove"});
+ synthesizePointer(target, rectTg.width/3, rectTg.height/7, {type: "pointerup"});
+ synthesizePointer(target, rectTg.width/3, rectTg.height/9, {type: "pointermove"});
+ finishTest();
+ }
+
+ function finishTest() {
+ parent.is(test_basketLeave, 0, "Part2: basket should not receive pointerleave event after pointer capturing");
+ parent.is(test_targetDown, 1, "Part2: target should receive pointerdown event");
+ parent.is(test_targetGotCapture, 1, "Part2: target should receive gotpointercapture event");
+ parent.is(test_targetLostCapture, 1, "Part2: target should receive lostpointercapture event");
+ parent.is(test_targetLeave, 1, "Part2: target should receive pointerleave event");
+ parent.is(test_childLeave, 0, "Part2: child should not receive pointerleave event after pointer capturing");
+ parent.is(test_listenerLeave, 0, "Part2: listener should not receive pointerleave event after pointer capturing");
+ logger("finishTest");
+ parent.finishTest();
+ }
+
+ function logger(message) {
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162990">Mozilla Bug 1162990 Test 2</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="basket">div id=basket
+ <div id="target">div id=target
+ <div id="child">div id=child</div>
+ </div>
+ </div>
+ <div id="listener">div id=listener</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1226904.html b/layout/base/tests/bug1226904.html
new file mode 100644
index 000000000..646608b1c
--- /dev/null
+++ b/layout/base/tests/bug1226904.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+</head>
+<style>
+ #container {
+ transform-style: preserve-3d;
+ transform: translate3d(400px, 0px, 10px);
+ background-color: black;
+ }
+ #separator {
+ width: 400px;
+ height: 400px;
+ background-color: green;
+ }
+ #transformed {
+ width: 400px;
+ height: 400px;
+ background-color: blue;
+ transform: translate3d(-400px, 0px, 10px);
+ }
+</style>
+<body onload="run()">
+<div>
+ <div id="container">
+ <div id="separator"></div>
+ <div id="transformed"></div>
+ </div>
+</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-1-ref.html b/layout/base/tests/bug1237236-1-ref.html
new file mode 100644
index 000000000..d8bca78b7
--- /dev/null
+++ b/layout/base/tests/bug1237236-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_DOWN", { }); // move caret to the second line
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-1.html b/layout/base/tests/bug1237236-1.html
new file mode 100644
index 000000000..6ed32fea3
--- /dev/null
+++ b/layout/base/tests/bug1237236-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_DOWN", { shiftKey: true }); // select first line including the newline
+ synthesizeKey("VK_RIGHT", { }); // collapse to the end of the selection
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-2-ref.html b/layout/base/tests/bug1237236-2-ref.html
new file mode 100644
index 000000000..141b33f6e
--- /dev/null
+++ b/layout/base/tests/bug1237236-2-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-2.html b/layout/base/tests/bug1237236-2.html
new file mode 100644
index 000000000..8b7707fa1
--- /dev/null
+++ b/layout/base/tests/bug1237236-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1237236</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre.firstChild, 4/*after the newline*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-1-ref.html b/layout/base/tests/bug1258308-1-ref.html
new file mode 100644
index 000000000..3c975569c
--- /dev/null
+++ b/layout/base/tests/bug1258308-1-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1258308</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ // caret should now be at the start of the third line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def
+ghi</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-1.html b/layout/base/tests/bug1258308-1.html
new file mode 100644
index 000000000..3f6c25540
--- /dev/null
+++ b/layout/base/tests/bug1258308-1.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1258308</title>
+ <script>
+ function test1() {
+ const kIsMac = navigator.platform.indexOf("Mac") == 0;
+ synthesizeKey("VK_DOWN", {}); // go to the second line
+ // go to the end of the second line
+ if (kIsMac) {
+ synthesizeKey("VK_RIGHT", {accelKey: true});
+ } else {
+ synthesizeKey("VK_END", {});
+ }
+ synthesizeKey("VK_RIGHT", { shiftKey: true }); // select the newline
+ synthesizeKey("VK_RIGHT", {}); // collapse to the end of the selection
+ // caret should now be at the start of the third line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def
+ghi</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-2-ref.html b/layout/base/tests/bug1258308-2-ref.html
new file mode 100644
index 000000000..88659d3f4
--- /dev/null
+++ b/layout/base/tests/bug1258308-2-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1258308</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def
+ghi</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-2.html b/layout/base/tests/bug1258308-2.html
new file mode 100644
index 000000000..f1472dc66
--- /dev/null
+++ b/layout/base/tests/bug1258308-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1258308</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre.firstChild, 8/*after the 2nd line newline*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def
+ghi</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-1-ref.html b/layout/base/tests/bug1259949-1-ref.html
new file mode 100644
index 000000000..26b58f604
--- /dev/null
+++ b/layout/base/tests/bug1259949-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_DOWN", {});
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abcdef
+</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-1.html b/layout/base/tests/bug1259949-1.html
new file mode 100644
index 000000000..52304b427
--- /dev/null
+++ b/layout/base/tests/bug1259949-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {}); // caret is now between "c" and "d"
+ synthesizeKey("VK_DOWN", {shiftKey: true}); // select "def\n"
+ synthesizeKey("VK_RIGHT", {}); // collapse to the end of the selection
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abcdef
+</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-2-ref.html b/layout/base/tests/bug1259949-2-ref.html
new file mode 100644
index 000000000..8ca51a286
--- /dev/null
+++ b/layout/base/tests/bug1259949-2-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ synthesizeKey("VK_RIGHT", { });
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abcdef
+
+</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-2.html b/layout/base/tests/bug1259949-2.html
new file mode 100644
index 000000000..999cadda3
--- /dev/null
+++ b/layout/base/tests/bug1259949-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1259949</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre, 1/*after the text*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body onload="runTests()">
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abcdef
+<br></pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263288-ref.html b/layout/base/tests/bug1263288-ref.html
new file mode 100644
index 000000000..3213a2624
--- /dev/null
+++ b/layout/base/tests/bug1263288-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263288
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263288</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="readonly">xyz</div>
+<div id="editable" spellcheck=false contenteditable='true' style="outline: 1px solid;"><span>xyz</span><br></br></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263288.html b/layout/base/tests/bug1263288.html
new file mode 100644
index 000000000..121fe21e8
--- /dev/null
+++ b/layout/base/tests/bug1263288.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263288
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263288</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="readonly">xyz</div>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span contentEditable='false'>xyz<!-- comment --></span><br></br></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {});
+ synthesizeKey("VK_END", {});
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-1-ref.html b/layout/base/tests/bug1263357-1-ref.html
new file mode 100644
index 000000000..6b4e888cf
--- /dev/null
+++ b/layout/base/tests/bug1263357-1-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><tt>xyz<br></tt><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theP = document.getElementById("theP");
+ theDiv.focus();
+ sel.collapse(theP, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-1.html b/layout/base/tests/bug1263357-1.html
new file mode 100644
index 000000000..4369d05cb
--- /dev/null
+++ b/layout/base/tests/bug1263357-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><tt>xyz</tt><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ if (navigator.platform.indexOf("Win") == 0) {
+ synthesizeKey("VK_END", {});
+ } else {
+ // End key doesn't work as expected on Mac and Linux.
+ sel.modify("move", "right", "lineboundary");
+ }
+ synthesizeKey("VK_RETURN", {shiftKey: true});
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-2-ref.html b/layout/base/tests/bug1263357-2-ref.html
new file mode 100644
index 000000000..3625510e6
--- /dev/null
+++ b/layout/base/tests/bug1263357-2-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><font color=red><span>xyz<br></span></font><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theP = document.getElementById("theP");
+ theDiv.focus();
+ sel.collapse(theP, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-2.html b/layout/base/tests/bug1263357-2.html
new file mode 100644
index 000000000..b92a5e42e
--- /dev/null
+++ b/layout/base/tests/bug1263357-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><font color=red><span>xyz</span></font><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ if (navigator.platform.indexOf("Win") == 0) {
+ synthesizeKey("VK_END", {});
+ } else {
+ // End key doesn't work as expected on Mac and Linux.
+ sel.modify("move", "right", "lineboundary");
+ }
+ synthesizeKey("VK_RETURN", {shiftKey: true});
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-3-ref.html b/layout/base/tests/bug1263357-3-ref.html
new file mode 100644
index 000000000..fad9d5851
--- /dev/null
+++ b/layout/base/tests/bug1263357-3-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #3 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-3.html b/layout/base/tests/bug1263357-3.html
new file mode 100644
index 000000000..5311068b0
--- /dev/null
+++ b/layout/base/tests/bug1263357-3.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #3 for bug1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-4-ref.html b/layout/base/tests/bug1263357-4-ref.html
new file mode 100644
index 000000000..5b568d17d
--- /dev/null
+++ b/layout/base/tests/bug1263357-4-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #4 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-4.html b/layout/base/tests/bug1263357-4.html
new file mode 100644
index 000000000..dfd4618b6
--- /dev/null
+++ b/layout/base/tests/bug1263357-4.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #4 for bug1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-5-ref.html b/layout/base/tests/bug1263357-5-ref.html
new file mode 100644
index 000000000..02290aa77
--- /dev/null
+++ b/layout/base/tests/bug1263357-5-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #5 for bug 1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-5.html b/layout/base/tests/bug1263357-5.html
new file mode 100644
index 000000000..bab3cc65b
--- /dev/null
+++ b/layout/base/tests/bug1263357-5.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #5 for bug1263357</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug240933-1-ref.html b/layout/base/tests/bug240933-1-ref.html
new file mode 100644
index 000000000..778a0647b
--- /dev/null
+++ b/layout/base/tests/bug240933-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML><html>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none">
+
+</textarea>
+<script>
+ var t = document.getElementById("t");
+ t.selectionStart = t.selectionEnd = t.value.length;
+ t.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug240933-1.html b/layout/base/tests/bug240933-1.html
new file mode 100644
index 000000000..ce96d4d09
--- /dev/null
+++ b/layout/base/tests/bug240933-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RETURN'); // press Enter once
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug240933-2.html b/layout/base/tests/bug240933-2.html
new file mode 100644
index 000000000..6b95cf1a9
--- /dev/null
+++ b/layout/base/tests/bug240933-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RETURN'); // press Enter twice
+ sendKey('RETURN');
+ sendKey('BACK_SPACE'); // press Backspace once
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug369950-subframe.xml b/layout/base/tests/bug369950-subframe.xml
new file mode 100644
index 000000000..8aed64cd4
--- /dev/null
+++ b/layout/base/tests/bug369950-subframe.xml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+</head>
+
+<body>
+
+<p>foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo <x></y></p>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-1-ref.html b/layout/base/tests/bug389321-1-ref.html
new file mode 100644
index 000000000..4fb2021da
--- /dev/null
+++ b/layout/base/tests/bug389321-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
+<script>
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Focus the span to put the caret at its beginning.
+ var area = document.getElementById('t');
+ area.focus();
+
+ // Do nothing else.
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-1.html b/layout/base/tests/bug389321-1.html
new file mode 100644
index 000000000..9915d9929
--- /dev/null
+++ b/layout/base/tests/bug389321-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
+<script>
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Focus the span to put the caret at its beginning.
+ var area = document.getElementById('t');
+ area.focus();
+
+ // Enter a character in the span then delete it.
+ sendChar("W");
+ sendKey("BACK_SPACE");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-2-ref.html b/layout/base/tests/bug389321-2-ref.html
new file mode 100644
index 000000000..09adebf7e
--- /dev/null
+++ b/layout/base/tests/bug389321-2-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-2.html b/layout/base/tests/bug389321-2.html
new file mode 100644
index 000000000..d878635a1
--- /dev/null
+++ b/layout/base/tests/bug389321-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 50px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-3-ref.html b/layout/base/tests/bug389321-3-ref.html
new file mode 100644
index 000000000..387cbf25d
--- /dev/null
+++ b/layout/base/tests/bug389321-3-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;">&nbsp;</div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-3.html b/layout/base/tests/bug389321-3.html
new file mode 100644
index 000000000..09adebf7e
--- /dev/null
+++ b/layout/base/tests/bug389321-3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug450930.xhtml b/layout/base/tests/bug450930.xhtml
new file mode 100644
index 000000000..36c284e49
--- /dev/null
+++ b/layout/base/tests/bug450930.xhtml
@@ -0,0 +1,181 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450930
+-->
+<head>
+ <title>Test for Bug 450930 (MozAfterPaint)</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runNext()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450930">Mozilla Bug 450930</a>
+<div id="display">
+ <div id="d" style="width:400px; height:200px;"></div>
+ <iframe id="iframe" style="width:400px; height:200px;"
+ src="data:text/html,&lt;div id='d'&gt;&lt;span style='margin-left:3px;'&gt;Hello&lt;/span&gt;
+ &lt;/div&gt;&lt;div style='margin-top:500px' id='d2'&gt;
+ &lt;span style='margin-left:3px;'&gt;Goodbye&lt;/span&gt;&lt;/div>"></iframe>
+ <svg:svg style="width:410px; height:210px;" id="svg">
+ <svg:foreignObject width="100%" height="100%">
+ <iframe id="iframe2" style="width:400px; height:200px;"
+ src="data:text/html,&lt;div id='d'&gt;&lt;span style='margin-left:3px;'&gt;Hello&lt;/span&gt;
+ &lt;/div&gt;&lt;div style='margin-top:500px' id='d2'&gt;
+ &lt;span style='margin-left:3px;'&gt;Goodbye&lt;/span&gt;&lt;/div>"></iframe>
+ </svg:foreignObject>
+ </svg:svg>
+</div>
+<div id="content" style="display: none">
+</div>
+
+
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+function flash(doc, name) {
+ var d = doc.getElementById(name);
+ d.style.backgroundColor = d.style.backgroundColor == "blue" ? "yellow" : "blue";
+ // Now flush out style changes in that document, since our event listeners
+ // seem to assume that things will work that way.
+ d.getBoundingClientRect();
+}
+
+function le(v1, v2, s) {
+ window.opener.ok(v1 <= v2, s + " (" + v1 + "," + v2 + ")");
+}
+
+function checkContains(r1, r2, s) {
+ le(Math.round(r1.left), Math.round(r2.left), "Left edges out" + s);
+ le(Math.round(r2.right), Math.round(r1.right), "Right edges out" + s);
+ le(Math.round(r1.top), Math.round(r2.top), "Top edges out" + s);
+ le(Math.round(r2.bottom), Math.round(r1.bottom), "Bottom edges out" + s);
+}
+
+function isRect(r1, r2) {
+ return (Math.abs(r1.left - r2.left) <= 1 ||
+ Math.abs(r1.right - r2.right) <= 1 ||
+ Math.abs(r1.top - r2.top) <= 1 ||
+ Math.abs(r1.bottom - r2.bottom) <= 1);
+}
+
+function isRectInList(r, list) {
+ for (var i = 0; i < list.length; ++i) {
+ if (isRect(r, list[i]))
+ return true;
+ }
+ return false;
+}
+
+function doesRectContain(r1, r2) {
+ return Math.floor(r1.left) <= r2.left && r2.right <= Math.ceil(r1.right) &&
+ Math.floor(r1.top) <= r2.top && r2.bottom <= Math.ceil(r1.bottom);
+}
+
+function rectToString(r) {
+ return "(" + r.left + "," + r.top + "," + r.right + "," + r.bottom + ")";
+}
+
+function doesRectContainListElement(r, list) {
+ dump("Incoming rect: " + rectToString(r) + "\n");
+ for (var i = 0; i < list.length; ++i) {
+ dump("List rect " + i + ": " + rectToString(list[i]));
+ if (doesRectContain(r, list[i])) {
+ dump(" FOUND\n");
+ return true;
+ }
+ dump("\n");
+ }
+ dump("NOT FOUND\n");
+ return false;
+}
+
+function checkGotSubdoc(list, container) {
+ var r = container.getBoundingClientRect();
+ return doesRectContainListElement(r, list);
+}
+
+function runTest1() {
+ // test basic functionality
+ var iterations = 0;
+ var foundExactRect = false;
+
+ function listener(event) {
+ var r = SpecialPowers.wrap(event).boundingClientRect;
+ var bounds = document.getElementById('d').getBoundingClientRect();
+ checkContains(r, bounds, "");
+ if (isRectInList(bounds, SpecialPowers.wrap(event).clientRects)) {
+ foundExactRect = true;
+ }
+ window.removeEventListener("MozAfterPaint", listener, false);
+ ++iterations;
+ if (iterations < 4) {
+ setTimeout(triggerPaint, 100);
+ } else {
+ window.opener.ok(foundExactRect, "Found exact rect");
+ runNext();
+ }
+ }
+
+ function triggerPaint() {
+ window.addEventListener("MozAfterPaint", listener, false);
+ flash(document, 'd');
+ window.opener.ok(true, "trigger test1 paint");
+ }
+ triggerPaint();
+}
+
+function runTest2(frameID, containerID) {
+ // test reporting of painting in subdocuments
+ var fired = 0;
+ var gotSubdocPrivileged = false;
+ var iframe = document.getElementById(frameID);
+ var container = document.getElementById(containerID);
+
+ function listener(event) {
+ if (checkGotSubdoc(SpecialPowers.wrap(event).clientRects, container))
+ gotSubdocPrivileged = true;
+ if (SpecialPowers.wrap(event).clientRects.length > 0) {
+ if (++fired == 1)
+ setTimeout(check, 100);
+ }
+ }
+
+ function check() {
+ window.opener.is(fired, 1, "Wrong event count (" + frameID + ")");
+ window.opener.ok(gotSubdocPrivileged, "Didn't get subdoc invalidation while we were privileged (" + frameID + ")");
+ window.removeEventListener("MozAfterPaint", listener, false);
+ runNext();
+ }
+
+ function triggerPaint() {
+ window.addEventListener("MozAfterPaint", listener, false);
+ document.body.offsetTop;
+ flash(iframe.contentDocument, 'd');
+ }
+ triggerPaint();
+}
+
+var test = 0;
+var tests = [runTest1,
+ function() { runTest2("iframe", "iframe") },
+ function() { runTest2("iframe2", "svg") }];
+function runNext() {
+ if (SpecialPowers.DOMWindowUtils.isMozAfterPaintPending) {
+ // Wait until there are no pending paints before trying to run tests
+ setTimeout(runNext, 100);
+ return;
+ }
+ if (test < tests.length) {
+ ++test;
+ tests[test - 1]();
+ } else {
+ window.opener.finishTests();
+ }
+}
+
+
+]]></script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug482484-ref.html b/layout/base/tests/bug482484-ref.html
new file mode 100644
index 000000000..c8b8c2bab
--- /dev/null
+++ b/layout/base/tests/bug482484-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head></head>
+<body>
+<div contentEditable="true" id="div" spellcheck="false"><p id="p">ABC</p></div>
+<script>
+ // Position the caret after the "A"
+ var div = document.getElementById('div');
+ var p = document.getElementById('p');
+ div.focus();
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 1)
+ range.setEnd(p.firstChild, 1);
+ sel.addRange(range);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug482484.html b/layout/base/tests/bug482484.html
new file mode 100644
index 000000000..f1de00799
--- /dev/null
+++ b/layout/base/tests/bug482484.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contentEditable="true" id="div" spellcheck="false"><p id="p">BC</p></div>
+<script>
+ // Position the caret before the "B"
+ var div = document.getElementById('div');
+ div.focus();
+ var p = document.getElementById('p');
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 0)
+ range.setEnd(p.firstChild, 0);
+ sel.addRange(range);
+
+ sendKey('UP'); // move UP
+ sendChar('A'); // insert "A"
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug503399-ref.html b/layout/base/tests/bug503399-ref.html
new file mode 100644
index 000000000..f165b1559
--- /dev/null
+++ b/layout/base/tests/bug503399-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 503399</title>
+ <style type="text/css">
+ html, body {
+ color: black;
+ background-color: white;
+ font: 16px monospace;
+ }
+ p {
+ text-align: justify;
+ max-width: 180px;
+ height: 1em;
+ overflow: hidden;
+ position: relative;
+ }
+ span {
+ display: inline-block;
+ border-left: 1px solid black;
+ position: absolute;
+ height: 100%;
+ }
+ </style>
+ <script>
+ var done = false;
+ function runTest(p) {
+ if (done)
+ return;
+ try {
+ var r = window.getSelection().getRangeAt(0);
+ r.setStart(p.childNodes[0],14);
+ r.setEnd(p.childNodes[0],14);
+ } catch (e) {}
+ document.documentElement.removeAttribute('class');
+ done = true;
+ }
+ </script>
+</head>
+<body onload="var p = document.getElementsByTagName('p')[0]; p.focus(); setTimeout(function(){runTest(p)},1000)">
+ <p onfocus="runTest(this)" contentEditable="true">&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;<span></span> &nbsp;&nbsp; &nbsp;&nbsp;</p>
+</body>
+</html>
diff --git a/layout/base/tests/bug503399.html b/layout/base/tests/bug503399.html
new file mode 100644
index 000000000..5857cf27d
--- /dev/null
+++ b/layout/base/tests/bug503399.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 503399</title>
+ <style type="text/css">
+ html, body {
+ color: black;
+ background-color: white;
+ font: 16px monospace;
+ }
+ p {
+ text-align: justify;
+ max-width: 180px;
+ height: 1em;
+ overflow: hidden;
+ position: relative;
+ }
+ span {
+ display: inline-block;
+ position: absolute;
+ height: 100%;
+ }
+ </style>
+ <script>
+ var done = false;
+ function runTest(p) {
+ if (done)
+ return;
+ try {
+ var r = window.getSelection().getRangeAt(0);
+ r.setStart(p.childNodes[0],14);
+ r.setEnd(p.childNodes[0],14);
+ } catch (e) {}
+ document.documentElement.removeAttribute('class');
+ done = true;
+ }
+ </script>
+</head>
+<body onload="var p = document.getElementsByTagName('p')[0]; p.focus(); setTimeout(function(){runTest(p)},1000)">
+ <p onfocus="runTest(this)" contentEditable="true">&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;<span></span> &nbsp;&nbsp; &nbsp;&nbsp;</p>
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-1-ref.html b/layout/base/tests/bug512295-1-ref.html
new file mode 100644
index 000000000..b2f8201c7
--- /dev/null
+++ b/layout/base/tests/bug512295-1-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+x
+<script>
+ // Position the caret at the end of the P element
+ var p = document.getElementById('p');
+ var div = p.parentNode;
+ div.focus();
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(div, function () {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p, p.childNodes.length);
+ range.setEnd(p, p.childNodes.length);
+ sel.addRange(range);
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-1.html b/layout/base/tests/bug512295-1.html
new file mode 100644
index 000000000..b69974d6c
--- /dev/null
+++ b/layout/base/tests/bug512295-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+x
+<script>
+ // Position the caret after "A"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var p = document.getElementById('p');
+ var t = p.firstChild;
+ range.setStart(t, 1);
+ range.setEnd(t, 1);
+ sel.addRange(range);
+ p.parentNode.focus();
+
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(p.parentNode, function () {
+ sendKey('DOWN'); // now after "1"
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-2-ref.html b/layout/base/tests/bug512295-2-ref.html
new file mode 100644
index 000000000..dddf935e4
--- /dev/null
+++ b/layout/base/tests/bug512295-2-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+<script>
+ // Position the caret before the "A"
+ var p = document.getElementById('p');
+ var div = p.parentNode;
+ div.focus();
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(div, function () {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 0);
+ range.setEnd(p.firstChild, 0);
+ sel.addRange(range);
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-2.html b/layout/base/tests/bug512295-2.html
new file mode 100644
index 000000000..3d64c44a6
--- /dev/null
+++ b/layout/base/tests/bug512295-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+<script>
+ // Position the caret after "A"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var p = document.getElementById('p');
+ var t = p.firstChild;
+ range.setStart(t, 1);
+ range.setEnd(t, 1);
+ sel.addRange(range);
+ p.parentNode.focus();
+
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(p.parentNode, function () {
+ sendKey('DOWN'); // now after "1"
+ sendKey('DOWN'); // now below the P element
+ sendKey('UP'); // now before the "1"
+ sendKey('UP'); // now before the "A"
+ sendKey('UP'); // now before the "A"
+ sendKey('UP'); // now before the "A"
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug558663.html b/layout/base/tests/bug558663.html
new file mode 100644
index 000000000..ecf0fc6c6
--- /dev/null
+++ b/layout/base/tests/bug558663.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=558663
+-->
+<head>
+ <title>Test for Bug 558663</title>
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=558663">Mozilla Bug 558663</a></p>
+
+ <!-- 20x20 of red -->
+<iframe id="iframe" src="data:text/html,<img id='image' border='0' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC'>"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 558663 **/
+var ok = parent.ok;
+var SimpleTest = parent.SimpleTest;
+var compareSnapshots = parent.compareSnapshots;
+var snapshotWindow = parent.snapshotWindow;
+var synthesizeMouse = parent.synthesizeMouse;
+
+window.addEventListener("load", runTest, false);
+
+function checkSnapshots(s1, s2, shouldBeEqual, testName) {
+ var res = compareSnapshots(s1, s2, shouldBeEqual);
+ if (res[0]) {
+ ok(true, testName + " snapshots compare correctly");
+ } else {
+ ok(false, testName + " snapshots compare incorrectly. snapshot 1: " +
+ res[1] + " snapshot 2: " + res[2]);
+ }
+}
+
+function runTest() {
+ document.getElementById("iframe").contentWindow.document.designMode = "on";
+
+ // The editor requires the event loop to spin after you turn on design mode
+ // before it takes effect.
+ setTimeout(continueTest, 100);
+}
+
+function continueTest() {
+ var win = document.getElementById("iframe").contentWindow;
+ var doc = win.document;
+ var image = doc.getElementById("image");
+
+ // We want to test that clicking on the image and then clicking on one of the
+ // draggers doesn't make the draggers disappear.
+
+ // clean snapshot
+ var before = snapshotWindow(win);
+
+ // click to get the draggers
+ synthesizeMouse(image, 1, 1, {type: "mousedown"}, win);
+ synthesizeMouse(image, 1, 1, {type: "mouseup"}, win);
+
+ // mouse over a dragger will change its color, so move the mouse away
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousemove"}, win);
+
+ // snapshot with hopefully draggers
+ var middle = snapshotWindow(win);
+
+ // clicking on the top left dragger shouldn't change anything
+ synthesizeMouse(image, 1, 1, {type: "mousedown"}, win);
+ synthesizeMouse(image, 1, 1, {type: "mouseup"}, win);
+
+ // mouse over a dragger will change its color, so move the mouse away
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousemove"}, win);
+
+ // snapshot with hopefully draggers again
+ var middle2 = snapshotWindow(win);
+
+ // click outside the image (but inside the document) to unselect it
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousedown"}, win);
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mouseup"}, win);
+
+ // and then click outside the document so we don't draw a caret
+ synthesizeMouse(document.documentElement, 1, 1, {type: "mousedown"}, window);
+ synthesizeMouse(document.documentElement, 1, 1, {type: "mouseup"}, window);
+
+ // hopefully clean snapshot
+ var end = snapshotWindow(win);
+
+ // before == end && middle == middle2 && before/end != middle/middle2
+ checkSnapshots(before, end, true, "before and after should be the same")
+ checkSnapshots(middle, middle2, true, "middle two should be the same");
+ checkSnapshots(before, middle, false, "before and middle should not be the same");
+ checkSnapshots(before, middle2, false, "before and middle2 should not be the same");
+ checkSnapshots(middle, end, false, "middle and end should not be the same");
+ checkSnapshots(middle2, end, false, "middle2 and end should not be the same");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug583889_inner1.html b/layout/base/tests/bug583889_inner1.html
new file mode 100644
index 000000000..85f1bfe3f
--- /dev/null
+++ b/layout/base/tests/bug583889_inner1.html
@@ -0,0 +1,67 @@
+<html>
+<body>
+<iframe id="inner" style="height: 10px; width: 10px"></iframe>
+<div style="width: 1000px; height: 1000px"></div>
+<script type="application/javascript;version=1.8">
+function grabEventAndGo(event) {
+ gen.send(event);
+}
+
+function waitAsync() {
+ setTimeout(function() { gen.next() }, 0);
+}
+
+function postPos() {
+ parent.postMessage(JSON.stringify({ top: document.body.scrollTop,
+ left: document.body.scrollLeft }),
+ "*");
+}
+
+function runTest() {
+ var inner = document.getElementById("inner");
+ window.onload = grabEventAndGo;
+ // Wait for onLoad event.
+ yield;
+
+ document.body.scrollTop = 300;
+ document.body.scrollLeft = 300;
+
+ postPos();
+
+ inner.src = "bug583889_inner2.html#id1";
+ inner.onload = grabEventAndGo;
+ // Let parent process sent message.
+ // Wait for onLoad event from 'inner' iframe.
+ yield;
+
+ postPos();
+
+ inner.onload = null;
+ dump("hi\n");
+ inner.contentWindow.location = "bug583889_inner2.html#id2"
+ waitAsync();
+ // Let parent process sent message.
+ // Let 'inner' iframe update itself.
+ yield;
+
+ postPos();
+
+ inner.contentWindow.location.hash = "#id3"
+ waitAsync();
+ // Let parent process sent message.
+ // Let 'inner' iframe update itself.
+ yield;
+
+ postPos();
+
+ parent.postMessage("done", "*");
+ // Let parent process sent messages.
+ // "End" generator.
+ yield;
+}
+
+var gen = runTest();
+gen.next();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug583889_inner2.html b/layout/base/tests/bug583889_inner2.html
new file mode 100644
index 000000000..ce63f54cf
--- /dev/null
+++ b/layout/base/tests/bug583889_inner2.html
@@ -0,0 +1,5 @@
+<body>
+<a id="id1">link 1</a>
+<a id="id2">link 2</a>
+<a id="id3">link 3</a>
+</body>
diff --git a/layout/base/tests/bug585922-ref.html b/layout/base/tests/bug585922-ref.html
new file mode 100644
index 000000000..76827f202
--- /dev/null
+++ b/layout/base/tests/bug585922-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="doTest()">
+ <input type=text style="-moz-appearance: none">
+ <script>
+ function doTest() {
+ var d = document.querySelector("input");
+ d.value = "b";
+ d.focus();
+ var editor = SpecialPowers.wrap(d).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
+ var sel = editor.selection;
+ var t = editor.rootElement.firstChild;
+ sel.collapse(t, 1); // put the caret at the end of the textbox
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug585922.html b/layout/base/tests/bug585922.html
new file mode 100644
index 000000000..e929d6710
--- /dev/null
+++ b/layout/base/tests/bug585922.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="doTest()">
+ <input type=text style="-moz-appearance: none">
+ <script>
+ function doTest() {
+ function enableCaret(aEnable) {
+ var selCon = editor.selectionController;
+ selCon.setCaretEnabled(aEnable);
+ }
+
+ var d = document.querySelector("input");
+ d.value = "a";
+ d.focus();
+ var editor = SpecialPowers.wrap(d).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
+ var sel = editor.selection;
+ var t = editor.rootElement.firstChild;
+ sel.collapse(t, 1); // put the caret at the end of the div
+ setTimeout(function() {
+ enableCaret(false);enableCaret(true);// force a caret display
+ enableCaret(false); // hide the caret
+ t.replaceData(0, 1, "b"); // replace the text node data
+ // at this point, the selection is collapsed to offset 0
+ synthesizeQuerySelectedText(); // call nsCaret::GetGeometry
+ sel.collapse(t, 1); // put the caret at the end again
+ enableCaret(true); // show the caret again
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug597519-1-ref.html b/layout/base/tests/bug597519-1-ref.html
new file mode 100644
index 000000000..e11eb0c96
--- /dev/null
+++ b/layout/base/tests/bug597519-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none">ab
+</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug597519-1.html b/layout/base/tests/bug597519-1.html
new file mode 100644
index 000000000..a33084f15
--- /dev/null
+++ b/layout/base/tests/bug597519-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea maxlength="3" spellcheck="false" style="-moz-appearance: none"></textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ synthesizeKey("a", {});
+ synthesizeKey("b", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("c", {});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-1-ref.html b/layout/base/tests/bug602141-1-ref.html
new file mode 100644
index 000000000..64cbd58c3
--- /dev/null
+++ b/layout/base/tests/bug602141-1-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span contenteditable="true" spellcheck="false">navigable__</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-1.html b/layout/base/tests/bug602141-1.html
new file mode 100644
index 000000000..5d3fa7bbe
--- /dev/null
+++ b/layout/base/tests/bug602141-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable="true" spellcheck="false">navigable__</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-2-ref.html b/layout/base/tests/bug602141-2-ref.html
new file mode 100644
index 000000000..f54518a02
--- /dev/null
+++ b/layout/base/tests/bug602141-2-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span id="x" contenteditable="true" spellcheck="false">navigable__|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 13);
+ range.setEnd(t, 13);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-2.html b/layout/base/tests/bug602141-2.html
new file mode 100644
index 000000000..57dff22a7
--- /dev/null
+++ b/layout/base/tests/bug602141-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span id="x" contenteditable="true" spellcheck="false">navigable__|</span><br />
+<script>
+ document.getElementById('x').appendChild(document.createTextNode('unnavigable'));
+
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 12);
+ range.setEnd(t, 12);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-3-ref.html b/layout/base/tests/bug602141-3-ref.html
new file mode 100644
index 000000000..8d39318cc
--- /dev/null
+++ b/layout/base/tests/bug602141-3-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+noteditable<span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-3.html b/layout/base/tests/bug602141-3.html
new file mode 100644
index 000000000..f1d41919a
--- /dev/null
+++ b/layout/base/tests/bug602141-3.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+noteditable<span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-4-ref.html b/layout/base/tests/bug602141-4-ref.html
new file mode 100644
index 000000000..c67986c5f
--- /dev/null
+++ b/layout/base/tests/bug602141-4-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span>not editable</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span>
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-4.html b/layout/base/tests/bug602141-4.html
new file mode 100644
index 000000000..7db22db07
--- /dev/null
+++ b/layout/base/tests/bug602141-4.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span>not editable</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span>
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-1.html b/layout/base/tests/bug612271-1.html
new file mode 100644
index 000000000..4c511b31e
--- /dev/null
+++ b/layout/base/tests/bug612271-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeydown="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-2.html b/layout/base/tests/bug612271-2.html
new file mode 100644
index 000000000..5c3754794
--- /dev/null
+++ b/layout/base/tests/bug612271-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeypress="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-3.html b/layout/base/tests/bug612271-3.html
new file mode 100644
index 000000000..29be02693
--- /dev/null
+++ b/layout/base/tests/bug612271-3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeyup="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-ref.html b/layout/base/tests/bug612271-ref.html
new file mode 100644
index 000000000..e1472c8c4
--- /dev/null
+++ b/layout/base/tests/bug612271-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript">
+ function loaded() {
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = 4;
+ }
+ </script>
+</head>
+<body onload="loaded()">
+ <textarea style="height: 200px; display: block; -moz-appearance: none" spellcheck="false"
+ >foo
+</textarea>
+ 4 - 4
+</body>
+</html>
diff --git a/layout/base/tests/bug613433-1.html b/layout/base/tests/bug613433-1.html
new file mode 100644
index 000000000..37e83a78b
--- /dev/null
+++ b/layout/base/tests/bug613433-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow-x: auto;
+ }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-2.html b/layout/base/tests/bug613433-2.html
new file mode 100644
index 000000000..9eb093ff4
--- /dev/null
+++ b/layout/base/tests/bug613433-2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow-y: auto;
+ }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-3.html b/layout/base/tests/bug613433-3.html
new file mode 100644
index 000000000..e7d1bc560
--- /dev/null
+++ b/layout/base/tests/bug613433-3.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow: auto;
+ }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-ref.html b/layout/base/tests/bug613433-ref.html
new file mode 100644
index 000000000..f4a2ab3b6
--- /dev/null
+++ b/layout/base/tests/bug613433-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ }
+ </style>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ }
+ function focusTriggered() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div contenteditable onfocus="focusTriggered()"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613807-1-ref.html b/layout/base/tests/bug613807-1-ref.html
new file mode 100644
index 000000000..b47a572ea
--- /dev/null
+++ b/layout/base/tests/bug613807-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="document.querySelector('textarea').focus()">
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+</body>
+</html>
diff --git a/layout/base/tests/bug613807-1.html b/layout/base/tests/bug613807-1.html
new file mode 100644
index 000000000..145ea5603
--- /dev/null
+++ b/layout/base/tests/bug613807-1.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ if (typeof(addLoadEvent) == 'undefined') {
+ _newCallStack = function(path) {
+ var rval = function () {
+ var callStack = arguments.callee.callStack;
+ for (var i = 0; i < callStack.length; i++) {
+ if (callStack[i].apply(this, arguments) === false) {
+ break;
+ }
+ }
+ try {
+ this[path] = null;
+ } catch (e) {
+ // pass
+ }
+ };
+ rval.callStack = [];
+ return rval;
+ };
+ function addLoadEvent(func) {
+ var existing = window["onload"];
+ var regfunc = existing;
+ if (!(typeof(existing) == 'function'
+ && typeof(existing.callStack) == "object"
+ && existing.callStack !== null)) {
+ regfunc = _newCallStack("onload");
+ if (typeof(existing) == 'function') {
+ regfunc.callStack.push(existing);
+ }
+ window["onload"] = regfunc;
+ }
+ regfunc.callStack.push(func);
+ };
+ }
+
+ addLoadEvent(function() {
+ var area = document.getElementById('t');
+ area.focus();
+
+ var domWindowUtils = SpecialPowers.getDOMWindowUtils(window);
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u306D",
+ clauses: [
+ { length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ });
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u306D\u3053",
+ clauses: [
+ { length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 2, length: 0 }
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u732B",
+ clauses: [
+ { length: 1, attr: COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ document.body.clientWidth;
+
+ // undo
+ synthesizeKey("Z", {accelKey: true});
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug632215-1.html b/layout/base/tests/bug632215-1.html
new file mode 100644
index 000000000..7def5962a
--- /dev/null
+++ b/layout/base/tests/bug632215-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <iframe src="data:text/html,<body spellcheck=false></body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ var s = w.getSelection();
+ i.focus();
+ d.body.contentEditable = true;
+ d.body.contentEditable = false;
+ d.designMode = "off";
+ d.designMode = "on";
+ d.body.focus();
+ synthesizeKey("x", {});
+ s.collapse(d.body.firstChild, 1);
+ synthesizeKey("x", {});
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug632215-2.html b/layout/base/tests/bug632215-2.html
new file mode 100644
index 000000000..a05b942a4
--- /dev/null
+++ b/layout/base/tests/bug632215-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <iframe src="data:text/html,<body contenteditable spellcheck=false></body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ var s = w.getSelection();
+ i.focus();
+ d.body.contentEditable = false;
+ d.designMode = "off";
+ d.designMode = "on";
+ d.body.focus();
+ synthesizeKey("x", {});
+ s.collapse(d.body.firstChild, 1);
+ synthesizeKey("x", {});
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug632215-ref.html b/layout/base/tests/bug632215-ref.html
new file mode 100644
index 000000000..980b5c46a
--- /dev/null
+++ b/layout/base/tests/bug632215-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe src="data:text/html,<body spellcheck=false>xx</body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ d.designMode = "on";
+ i.focus();
+ d.body.focus();
+ w.getSelection().collapse(d.body.firstChild, 2);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug633044-1-ref.html b/layout/base/tests/bug633044-1-ref.html
new file mode 100644
index 000000000..5a341c4e6
--- /dev/null
+++ b/layout/base/tests/bug633044-1-ref.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script>
+ onload = function() {
+ var el;
+ while (el = document.querySelector("br")) {
+ el.parentNode.removeChild(el);
+ }
+ focus();
+ document.body.focus();
+ getSelection().collapse(document.body.firstChild, 0);
+ }
+ </script>
+</head>
+
+<body style="white-space:pre-wrap;" contenteditable></body></html>
diff --git a/layout/base/tests/bug633044-1.html b/layout/base/tests/bug633044-1.html
new file mode 100644
index 000000000..63ee5a781
--- /dev/null
+++ b/layout/base/tests/bug633044-1.html
@@ -0,0 +1,24 @@
+<html>
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ onload = function() {
+ var el;
+ while (el = document.querySelector("br")) {
+ el.parentNode.removeChild(el);
+ }
+ focus();
+ document.body.focus();
+ getSelection().collapse(document.body.firstChild, 0);
+
+ var range = window.getSelection().getRangeAt(0);
+ var el = document.createTextNode(" ");
+ range.insertNode(el);
+ el.parentNode.removeChild(el);
+
+ synthesizeKey("VK_UP", {});
+ }
+ </script>
+</head>
+
+<body style="white-space:pre-wrap;" contenteditable></body></html>
diff --git a/layout/base/tests/bug634406-1-ref.html b/layout/base/tests/bug634406-1-ref.html
new file mode 100644
index 000000000..87b42a9ed
--- /dev/null
+++ b/layout/base/tests/bug634406-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML><html>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none">ab</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug634406-1.html b/layout/base/tests/bug634406-1.html
new file mode 100644
index 000000000..141198b59
--- /dev/null
+++ b/layout/base/tests/bug634406-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML><html><head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none"></textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ synthesizeKey("a", {});
+ synthesizeKey("A", {accelKey: true});
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("b", {});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug644428-1-ref.html b/layout/base/tests/bug644428-1-ref.html
new file mode 100644
index 000000000..ee193b0bb
--- /dev/null
+++ b/layout/base/tests/bug644428-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="setupCaret()">
+ <div contenteditable>a</div>
+ <script>
+ function setupCaret() {
+ var div = document.querySelector("div");
+ div.focus();
+ var sel = window.getSelection();
+ sel.collapse(div, 1);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug644428-1.html b/layout/base/tests/bug644428-1.html
new file mode 100644
index 000000000..9fe4d17fe
--- /dev/null
+++ b/layout/base/tests/bug644428-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="setupCaret()" spellcheck="false">
+ <div contenteditable>a<span>b</span>c </div>
+ <script>
+ function setupCaret() {
+ var div = document.querySelector("div");
+ div.focus();
+ var sel = window.getSelection();
+ sel.collapse(div, 3);
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-1-ref.html b/layout/base/tests/bug646382-1-ref.html
new file mode 100644
index 000000000..03acde5b5
--- /dev/null
+++ b/layout/base/tests/bug646382-1-ref.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()" style="-moz-appearance: none">س</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-1.html b/layout/base/tests/bug646382-1.html
new file mode 100644
index 000000000..06335befa
--- /dev/null
+++ b/layout/base/tests/bug646382-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="typeIntoMe()" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ synthesizeKey("س", {});
+ synthesizeKey("VK_DOWN", {});
+ synthesizeKey("VK_DOWN", {});
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-2-ref.html b/layout/base/tests/bug646382-2-ref.html
new file mode 100644
index 000000000..fde995d41
--- /dev/null
+++ b/layout/base/tests/bug646382-2-ref.html
@@ -0,0 +1,14 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()" style="-moz-appearance: none">s</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-2.html b/layout/base/tests/bug646382-2.html
new file mode 100644
index 000000000..4852b8563
--- /dev/null
+++ b/layout/base/tests/bug646382-2.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="typeIntoMe()" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ synthesizeKey("s", {});
+ synthesizeKey("VK_DOWN", {});
+ synthesizeKey("VK_DOWN", {});
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-1-ref.html b/layout/base/tests/bug664087-1-ref.html
new file mode 100644
index 000000000..77de000b0
--- /dev/null
+++ b/layout/base/tests/bug664087-1-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea rows="3" onfocus="done()" spellcheck="false" style="-moz-appearance: none">×ב
+×’</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_LEFT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-1.html b/layout/base/tests/bug664087-1.html
new file mode 100644
index 000000000..ea12bcaad
--- /dev/null
+++ b/layout/base/tests/bug664087-1.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea rows="3" onfocus="typeIntoMe()" spellcheck="false" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ synthesizeKey("×", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("×’", {});
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_END", {});
+ synthesizeKey("ב", {});
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-2-ref.html b/layout/base/tests/bug664087-2-ref.html
new file mode 100644
index 000000000..52749fe44
--- /dev/null
+++ b/layout/base/tests/bug664087-2-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()" spellcheck="false" style="-moz-appearance: none">ab
+c</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {});
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-2.html b/layout/base/tests/bug664087-2.html
new file mode 100644
index 000000000..3baf2b165
--- /dev/null
+++ b/layout/base/tests/bug664087-2.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="typeIntoMe()" spellcheck="false" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ synthesizeKey("a", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("c", {});
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_END", {});
+ synthesizeKey("b", {});
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug682712-1-ref.html b/layout/base/tests/bug682712-1-ref.html
new file mode 100644
index 000000000..1e034fd91
--- /dev/null
+++ b/layout/base/tests/bug682712-1-ref.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <iframe src="data:text/html,<body contenteditable spellcheck=false>foo bar"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+
+ setTimeout(function() {
+ doc.body.focus();
+
+ // Now try to set the caret without moving it
+ win.getSelection().collapse(doc.body.firstChild, 1);
+
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug682712-1.html b/layout/base/tests/bug682712-1.html
new file mode 100644
index 000000000..9effc8fce
--- /dev/null
+++ b/layout/base/tests/bug682712-1.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe src="data:text/html,<body contenteditable spellcheck=false>foo bar"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+
+ // Reframe the iframe
+ iframe.style.display = "none";
+ document.body.clientWidth;
+ iframe.style.display = "";
+ document.body.clientWidth;
+
+ setTimeout(function() {
+ doc.body.focus();
+
+ // Now try to move the caret
+ win.getSelection().collapse(doc.body.firstChild, 0);
+ synthesizeKey("VK_RIGHT", {});
+
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug687297_a.html b/layout/base/tests/bug687297_a.html
new file mode 100644
index 000000000..af0010834
--- /dev/null
+++ b/layout/base/tests/bug687297_a.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_a(document.getElementById("test_content").clientHeight);
+ window.location.href = "bug687297_b.html";
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug687297_b.html b/layout/base/tests/bug687297_b.html
new file mode 100644
index 000000000..34f682354
--- /dev/null
+++ b/layout/base/tests/bug687297_b.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_b(document.getElementById("test_content").clientHeight);
+ window.location.href = "bug687297_c.html";
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug687297_c.html b/layout/base/tests/bug687297_c.html
new file mode 100644
index 000000000..ea029d24e
--- /dev/null
+++ b/layout/base/tests/bug687297_c.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_c(document.getElementById("test_content").clientHeight);
+ window.close();
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug746993-1-ref.html b/layout/base/tests/bug746993-1-ref.html
new file mode 100644
index 000000000..d65c6b142
--- /dev/null
+++ b/layout/base/tests/bug746993-1-ref.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe src="data:text/html,<body contenteditable spellcheck=false>Here's some text.<br /><br /><div></div></body>"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+ iframe.focus();
+ doc.body.focus();
+ win.getSelection().collapse(doc.body, 3);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug746993-1.html b/layout/base/tests/bug746993-1.html
new file mode 100644
index 000000000..7ed2ccedc
--- /dev/null
+++ b/layout/base/tests/bug746993-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe src="data:text/html,<body contenteditable spellcheck=false><br /><div></div></body>"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+ iframe.focus();
+ doc.body.focus();
+ win.getSelection().collapse(doc.body, 0);
+ sendString("Here's some text.");
+ synthesizeKey("VK_RETURN", {});
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug851445_helper.html b/layout/base/tests/bug851445_helper.html
new file mode 100644
index 000000000..dc4e4002e
--- /dev/null
+++ b/layout/base/tests/bug851445_helper.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body style="height:1000px">
+<script>
+var docElement = document.documentElement;
+docElement.style.display = 'none';
+docElement.offsetTop;
+docElement.style.display = '';
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug921928_event_target_iframe_apps_oop.html b/layout/base/tests/bug921928_event_target_iframe_apps_oop.html
new file mode 100644
index 000000000..2a6a7d5a6
--- /dev/null
+++ b/layout/base/tests/bug921928_event_target_iframe_apps_oop.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test companion for bug 921928</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/base/tests/bug923376-ref.html b/layout/base/tests/bug923376-ref.html
new file mode 100644
index 000000000..3a60cf195
--- /dev/null
+++ b/layout/base/tests/bug923376-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html class="reftest-wait"><div contenteditable>something missspelled<br>something elsed#</div>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+document.body.firstChild.focus();
+SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm",
+ window);
+onSpellCheck(document.body.firstChild, function() {
+ document.documentElement.removeAttribute("class");
+});
+</script>
diff --git a/layout/base/tests/bug923376.html b/layout/base/tests/bug923376.html
new file mode 100644
index 000000000..8a12a18bd
--- /dev/null
+++ b/layout/base/tests/bug923376.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html class="reftest-wait"><div contenteditable></div>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+var div = document.body.firstChild;
+div.focus();
+SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm",
+ window);
+onSpellCheck(div, function() {
+ div.innerHTML = 'something missspelled<br>something elsed#';
+ onSpellCheck(div, function() {
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug956530-1-ref.html b/layout/base/tests/bug956530-1-ref.html
new file mode 100644
index 000000000..d82c761bf
--- /dev/null
+++ b/layout/base/tests/bug956530-1-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <style>
+ /* eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ <script>
+ function test() {
+ focus();
+ var i = document.querySelector("input");
+ i.focus();
+ }
+ function finish() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ <body onload="setTimeout(test, 0)">
+ <input value="text text text text text"
+ onfocus="this.select(); finish();">
+ </body>
+</html>
diff --git a/layout/base/tests/bug956530-1.html b/layout/base/tests/bug956530-1.html
new file mode 100644
index 000000000..c4a223097
--- /dev/null
+++ b/layout/base/tests/bug956530-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <style>
+ /* eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ var i = document.querySelector("input");
+ i.setSelectionRange(i.value.length,i.value.length);
+ focus();
+ synthesizeMouseAtCenter(i, {});
+ setTimeout(function() {
+ synthesizeMouseAtCenter(document.body, {});
+ setTimeout(function() {
+ synthesizeMouseAtCenter(i, {});
+ }, 0);
+ }, 0);
+ }
+ function finish() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ <body onload="setTimeout(test, 0)">
+ <input value="text text text text text"
+ onfocus="this.select(); finish();">
+ </body>
+</html>
diff --git a/layout/base/tests/bug966992-1-ref.html b/layout/base/tests/bug966992-1-ref.html
new file mode 100644
index 000000000..c8fff7b5e
--- /dev/null
+++ b/layout/base/tests/bug966992-1-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:padding-box;
+}
+.rel { position:relative; }
+.mask1 { position:absolute; width:20px; background:white; top:0; bottom:0; right:0; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="rel block">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<span style="padding-right:20px">X</span><div class=mask1></div></div>
+<mask></mask>
+</div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-1.html b/layout/base/tests/bug966992-1.html
new file mode 100644
index 000000000..f630ff1c6
--- /dev/null
+++ b/layout/base/tests/bug966992-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:content-box;
+}
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div>
+<mask></mask>
+</div>
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-2-ref.html b/layout/base/tests/bug966992-2-ref.html
new file mode 100644
index 000000000..a619d579c
--- /dev/null
+++ b/layout/base/tests/bug966992-2-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:padding-box;
+ line-height:1px;
+}
+.rel { position:relative; }
+.mask1 { position:absolute; width:20px; background:white; top:0; bottom:0; right:0; }
+.mask2 { position:absolute; height:20px; background:white; top:0; left:40px; right:0; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="rel block">&nbsp;&nbsp;&nbsp;&nbsp;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<span style="padding-right:20px">X</span><div class=mask2></div><div class=mask1></div></div>
+<mask></mask>
+</div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-2.html b/layout/base/tests/bug966992-2.html
new file mode 100644
index 000000000..1a8919e55
--- /dev/null
+++ b/layout/base/tests/bug966992-2.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:content-box;
+ line-height:1px;
+}
+.rel { position:relative; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block">&nbsp;&nbsp;&nbsp;&nbsp;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div>
+<mask></mask>
+</div>
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-3-ref.html b/layout/base/tests/bug966992-3-ref.html
new file mode 100644
index 000000000..cefbc4f80
--- /dev/null
+++ b/layout/base/tests/bug966992-3-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+ div {
+ width: 100px; padding-right:50px; overflow-clip-box:padding-box;
+ overflow:hidden;
+ }
+.rel { position:relative; }
+.mask5 { position:absolute; height:40px; background:white; top:3px; left:0px; width:50px; }
+ </style>
+</head>
+<body>
+
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block"><span style="padding-right:50px">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</span></div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+x.scrollLeft=100000
+window.getSelection().collapse(x,1);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-3.html b/layout/base/tests/bug966992-3.html
new file mode 100644
index 000000000..2cc2af96a
--- /dev/null
+++ b/layout/base/tests/bug966992-3.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+ div {
+ width: 100px; padding-right:50px; overflow-clip-box:content-box;
+ overflow:hidden;
+ }
+ </style>
+</head>
+<body>
+
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+x.scrollLeft=100000
+window.getSelection().collapse(x,1);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug968148_inner.html b/layout/base/tests/bug968148_inner.html
new file mode 100644
index 000000000..7de693f73
--- /dev/null
+++ b/layout/base/tests/bug968148_inner.html
@@ -0,0 +1,295 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=968148
+-->
+<head>
+ <title>Test for Bug 968148</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .test {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ -moz-user-select: none;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968148">Mozilla Bug 968148</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 968148, test orignally copied from test_bug582771.html.
+ * Mouse functionality converted to pointer and all steps duplicated in order to run them in parallel for two different pointer Id's
+**/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+var test1d1;
+var test1d2;
+var test2d1;
+var test2d2;
+var test1d1pointermovecount = 0;
+var test1d2pointermovecount = 0;
+var test2d1pointermovecount = 0;
+var test2d2pointermovecount = 0;
+
+var test1d1pointerlostcapture = 0;
+var test1d2pointerlostcapture = 0;
+var test2d1pointerlostcapture = 0;
+var test2d2pointerlostcapture = 0;
+var test1d1pointergotcapture = 0;
+var test1d2pointergotcapture = 0;
+var test2d1pointergotcapture = 0;
+var test2d2pointergotcapture = 0;
+var test1PointerId = 1;
+var test2PointerId = 2;
+
+function sendPointerMove(el, id) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendPointerEvent('pointermove', rect.left + 5, rect.top + 5, 0, 0, 0, false, 0, SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH, id);
+}
+
+function sendPointerDown(el, id) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendPointerEvent('pointerdown', rect.left + 5, rect.top + 5, 0, 1, 0, false, 0, SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH, id);
+}
+
+function sendPointerUp(el, id) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendPointerEvent('pointerup', rect.left + 5, rect.top + 5, 0, 1, 0, false, 0, SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH, id);
+}
+
+function log(s) {
+ document.getElementById("l").textContent += s + "\n";
+}
+
+function test1d2Listener(e) {
+ log(e.type + ", " + e.target.id);
+ is(e.target, test1d2, "test1d2 should have got pointermove.");
+ ++test1d2pointermovecount;
+}
+
+function test2d2Listener(e) {
+ log(e.type + ", " + e.target.id);
+ is(e.target, test2d2, "test2d2 should have got pointermove.");
+ ++test2d2pointermovecount;
+}
+
+function test1d1DownListener(e) {
+ log(e.type + ", " + e.target.id);
+ test1d1.setPointerCapture(e.pointerId);
+}
+
+function test1d1MoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ test1d2.setPointerCapture(e.pointerId);
+}
+
+function test2d1DownListener(e) {
+ log(e.type + ", " + e.target.id);
+ test2d1.setPointerCapture(e.pointerId);
+}
+
+function test2d1MoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ test2d2.setPointerCapture(e.pointerId);
+}
+
+function test1d1PointerGotCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test1d1pointergotcapture;
+}
+
+function test1d1PointerLostCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test1d1pointerlostcapture;
+}
+
+function test2d1PointerGotCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test2d1pointergotcapture;
+}
+
+function test2d1PointerLostCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test2d1pointerlostcapture;
+}
+
+function test1d2PointerGotCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test1d2pointergotcapture;
+}
+
+function test1d2PointerLostCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test1d2pointerlostcapture;
+}
+
+function test2d2PointerGotCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test2d2pointergotcapture;
+}
+
+function test2d2PointerLostCapture(e) {
+ log(e.type + ", " + e.target.id);
+ ++test2d2pointerlostcapture;
+}
+
+function test1d1PointerMoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ ++test1d1pointermovecount;
+}
+
+function test2d1PointerMoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ ++test2d1pointermovecount;
+}
+
+function runTests() {
+ test1d1 = document.getElementById("test1d1");
+ test1d2 = document.getElementById("test1d2");
+ test2d1 = document.getElementById("test2d1");
+ test2d2 = document.getElementById("test2d2");
+
+ test1d2.addEventListener("pointermove", test1d2Listener, true);
+ test2d2.addEventListener("pointermove", test2d2Listener, true);
+
+ test1d1.addEventListener("gotpointercapture", test1d1PointerGotCapture, true);
+ test1d1.addEventListener("lostpointercapture", test1d1PointerLostCapture, true);
+
+ test2d1.addEventListener("gotpointercapture", test2d1PointerGotCapture, true);
+ test2d1.addEventListener("lostpointercapture", test2d1PointerLostCapture, true);
+
+ test1d2.addEventListener("gotpointercapture", test1d2PointerGotCapture, true);
+ test1d2.addEventListener("lostpointercapture", test1d2PointerLostCapture, true);
+
+ test2d2.addEventListener("gotpointercapture", test2d2PointerGotCapture, true);
+ test2d2.addEventListener("lostpointercapture", test2d2PointerLostCapture, true);
+
+ document.body.offsetLeft;
+ sendPointerMove(test1d2, test1PointerId);
+ sendPointerMove(test2d2, test2PointerId);
+ is(test1d2pointermovecount, 1, "Should have got pointermove");
+ is(test2d2pointermovecount, 1, "Should have got pointermove");
+
+ // This shouldn't enable capturing, since we're not in a right kind of
+ // event listener.
+ sendPointerDown(test1d1, test1PointerId);
+ sendPointerDown(test2d1, test2PointerId);
+
+ sendPointerMove(test1d2, test1PointerId);
+ sendPointerMove(test2d2, test2PointerId);
+
+ sendPointerUp(test1d1, test1PointerId);
+ sendPointerUp(test2d1, test2PointerId);
+
+ is(test1d2pointermovecount, 2, "Should have got pointermove");
+ is(test2d2pointermovecount, 2, "Should have got pointermove");
+
+ test1d1.addEventListener("pointerdown", test1d1DownListener, true);
+ test1d1.addEventListener("pointermove", test1d1PointerMoveListener, true);
+ test2d1.addEventListener("pointerdown", test2d1DownListener, true);
+ test2d1.addEventListener("pointermove", test2d1PointerMoveListener, true);
+
+ sendPointerDown(test1d1, test1PointerId);
+ sendPointerDown(test2d1, test2PointerId);
+ sendPointerMove(test1d2, test1PointerId);
+ sendPointerMove(test2d2, test2PointerId);
+ is(test1d2pointermovecount, 2, "Shouldn't have got pointermove");
+ is(test1d1pointermovecount, 1, "Should have got pointermove");
+ is(test1d1pointergotcapture, 1, "Should have got pointergotcapture");
+
+ is(test2d2pointermovecount, 2, "Shouldn't have got pointermove");
+ is(test2d1pointermovecount, 1, "Should have got pointermove");
+ is(test2d1pointergotcapture, 1, "Should have got pointergotcapture");
+
+ sendPointerUp(test1d1, test1PointerId);
+ sendPointerUp(test2d1, test2PointerId);
+ test1d1.removeEventListener("pointerdown", test1d1DownListener, true);
+ test1d1.removeEventListener("pointermove", test1d1PointerMoveListener, true);
+ test2d1.removeEventListener("pointerdown", test2d1DownListener, true);
+ test2d1.removeEventListener("pointermove", test2d1PointerMoveListener, true);
+
+ // Nothing should be capturing the event.
+ sendPointerMove(test1d2, test1PointerId);
+ sendPointerMove(test2d2, test2PointerId);
+
+ is(test1d2pointermovecount, 3, "Should have got pointermove");
+ is(test1d1pointerlostcapture, 1, "Should have got pointerlostcapture");
+ is(test2d2pointermovecount, 3, "Should have got pointermove");
+ is(test2d1pointerlostcapture, 1, "Should have got pointerlostcapture");
+
+ test1d1.addEventListener("pointermove", test1d1MoveListener, true);
+ test2d1.addEventListener("pointermove", test2d1MoveListener, true);
+
+ sendPointerDown(test1d1, test1PointerId);
+ sendPointerDown(test2d1, test2PointerId);
+
+ sendPointerMove(test1d1, test1PointerId); // This should call setPointerCapture to test1d2!
+ sendPointerMove(test2d1, test2PointerId); // This should call setPointerCapture to test2d2!
+
+ test1d1.removeEventListener("pointermove", test1d1MoveListener, true);
+ test1d1.addEventListener("pointermove", test1d1PointerMoveListener, true);
+
+ test2d1.removeEventListener("pointermove", test2d1MoveListener, true);
+ test2d1.addEventListener("pointermove", test2d1PointerMoveListener, true);
+
+ sendPointerMove(test1d1, test1PointerId); // This should send pointer event to test1d2.
+ sendPointerMove(test2d1, test2PointerId); // This should send pointer event to test2d2.
+
+ is(test1d1pointermovecount, 1, "Shouldn't have got pointermove");
+ is(test1d2pointermovecount, 4, "Should have got pointermove");
+ is(test1d2pointergotcapture, 1, "Should have got pointergotcapture");
+
+ is(test2d1pointermovecount, 1, "Shouldn't have got pointermove");
+ is(test2d2pointermovecount, 4, "Should have got pointermove");
+ is(test2d2pointergotcapture, 1, "Should have got pointergotcapture");
+
+ sendPointerUp(test1d1, test1PointerId);
+ sendPointerUp(test2d1, test2PointerId);
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ setTimeout(function() {
+ window.parent.postMessage("SimpleTest.finish();", "*");
+ }, 0);
+ }, 0);
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true],
+ ]
+ }, runTests);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div class="test" id="test1d1">&nbsp;</div><br><div class="test" id="test1d2">&nbsp;</div>
+<div class="test" id="test2d1">&nbsp;</div><br><div class="test" id="test2d2">&nbsp;</div>
+<pre id="l"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug970964_inner.html b/layout/base/tests/bug970964_inner.html
new file mode 100644
index 000000000..a9cba2916
--- /dev/null
+++ b/layout/base/tests/bug970964_inner.html
@@ -0,0 +1,342 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970964
+-->
+<head>
+ <title>Test for Bug 970964</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=970964">Mozilla Bug 970964</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 970964 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+function testtouch(aOptions) {
+ if (!aOptions)
+ aOptions = {};
+ this.identifier = aOptions.identifier || 0;
+ this.target = aOptions.target || 0;
+ this.page = aOptions.page || {x: 0, y: 0};
+ this.radius = aOptions.radius || {x: 0, y: 0};
+ this.rotationAngle = aOptions.rotationAngle || 0;
+ this.force = aOptions.force || 1;
+}
+
+function touchEvent(aOptions) {
+ if (!aOptions) {
+ aOptions = {};
+ }
+ this.ctrlKey = aOptions.ctrlKey || false;
+ this.altKey = aOptions.altKey || false;
+ this.shiftKey = aOptions.shiftKey || false;
+ this.metaKey = aOptions.metaKey || false;
+ this.touches = aOptions.touches || [];
+ this.targetTouches = aOptions.targetTouches || [];
+ this.changedTouches = aOptions.changedTouches || [];
+}
+
+function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
+ var ids = [], xs=[], ys=[], rxs = [], rys = [],
+ rotations = [], forces = [];
+
+ for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
+ for (var i = 0; i < aEvent[touchType].length; i++) {
+ if (ids.indexOf(aEvent[touchType][i].identifier) == -1) {
+ ids.push(aEvent[touchType][i].identifier);
+ xs.push(aEvent[touchType][i].page.x);
+ ys.push(aEvent[touchType][i].page.y);
+ rxs.push(aEvent[touchType][i].radius.x);
+ rys.push(aEvent[touchType][i].radius.y);
+ rotations.push(aEvent[touchType][i].rotationAngle);
+ forces.push(aEvent[touchType][i].force);
+ }
+ }
+ }
+ return windowUtils.sendTouchEvent(aType,
+ ids, xs, ys, rxs, rys,
+ rotations, forces,
+ ids.length, aModifiers, 0);
+}
+
+function getDefaultArgEvent(eventname) {
+ return new PointerEvent(eventname, {
+ bubbles: true, cancelable: true, view: window,
+ detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0,
+ ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
+ button: 0, relatedTarget: null, pointerId: 0
+ });
+}
+
+function getTouchEventForTarget(target, cwu, id) {
+ var bcr = target.getBoundingClientRect();
+ var touch = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: id,
+ });
+ var event = new touchEvent({
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch]
+ });
+ return event;
+}
+
+function runTests() {
+ var d0 = document.getElementById("d0");
+ var d1 = document.getElementById("d1");
+ var d2 = document.getElementById("d2");
+ var d3 = document.getElementById("d3");
+
+ // Test Pointer firing before any mouse/touch original source
+
+ var mouseDownTriggered = 0;
+ var pointerDownTriggered = 0;
+ var touchDownTriggered = 0;
+ var touchCancelTriggered = 0;
+ var pointerCancelTriggered = 0;
+
+ d0.onmousedown = function(e) {
+ mouseDownTriggered = 1;
+ is(pointerDownTriggered , 1, "Mouse event must be triggered after pointer event!");
+ };
+ d0.ontouchstart = function(e) {
+ touchDownTriggered = 1;
+ is(touchDownTriggered , 1, "Touch event must be triggered after pointer event!");
+ }
+ d0.ontouchcancel = function(e) {
+ touchCancelTriggered = 1;
+ is(pointerCancelTriggered, 1, "Touch cancel event must be triggered after pointer event!");
+ }
+ d0.onpointerdown = function(e) {
+ pointerDownTriggered = 1;
+ is(mouseDownTriggered, 0, "Pointer event must be triggered before mouse event!");
+ is(touchDownTriggered, 0, "Pointer event must be triggered before touch event!");
+ };
+ d0.addEventListener("pointercancel", function(ev) {
+ d0.removeEventListener("pointercancel", arguments.callee, false);
+ is(ev.pointerId, 0, "Correct default pointerId");
+ is(ev.bubbles, true, "bubbles should be true");
+ is(ev.cancelable, false, "pointercancel cancelable should be false ");
+ pointerCancelTriggered = 1;
+ is(touchCancelTriggered, 0, "Pointer event must be triggered before touch event!");
+ }, false);
+
+ // Test pointer event generated from mouse event
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ synthesizeMouse(d1, 3, 3, { type: "mousedown"});
+ synthesizeMouse(d1, 3, 3, { type: "mouseup"});
+
+ // Test pointer event generated from touch event
+ pointerDownTriggered = 0;
+ mouseDownTriggered = 0;
+
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+ var event1 = getTouchEventForTarget(d1, cwu, 0);
+ sendTouchEvent(cwu, "touchmove", event1, 0);
+ sendTouchEvent(cwu, "touchstart", event1, 0);
+ // Test Touch to Pointer Cancel
+ sendTouchEvent(cwu, "touchcancel", event1, 0);
+
+ // Check Pointer enter/leave from mouse generated event
+ var mouseEnterTriggered = 0;
+ var pointerEnterTriggered = 0;
+ d2.onpointerenter = function(e) {
+ pointerEnterTriggered = 1;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ is(mouseEnterTriggered, 0, "Pointer event must be triggered before mouse event!");
+ };
+ d2.onmouseenter = function(e) {
+ mouseEnterTriggered = 1;
+ is(pointerEnterTriggered , 1, "Mouse event must be triggered after pointer event!");
+ };
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ d2.onmouseenter = function(e) {}
+
+ // Test Multi Pointer enter/leave for pointers generated from Mouse and Touch at the same time
+ // Enter mouse and touch generated pointers to different elements
+ var d1enterCount = 0;
+ var d2enterCount = 0;
+ var d3enterCount = 0;
+ var d1leaveCount = 0;
+ var d2leaveCount = 0;
+ var d3leaveCount = 0;
+ var mousePointerEnterLeaveCount = 0;
+ var touchPointerEnterLeaveCount = 0;
+
+ var checkPointerType = function(pointerType) {
+ if (pointerType == "mouse") {
+ ++mousePointerEnterLeaveCount;
+ } else if (pointerType == "touch") {
+ ++touchPointerEnterLeaveCount;
+ }
+ };
+
+ d1.onpointerenter = function(e) {
+ ++d1enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerenter = function(e) {
+ ++d2enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerenter = function(e) {
+ ++d3enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d1.onpointerleave = function(e) {
+ ++d1leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerleave = function(e) {
+ ++d2leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerleave = function(e) {
+ ++d3leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d3, cwu, 3), 0);
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 2, "Wrong mouse enterLeave count for!");
+
+ is(d1enterCount, 1, "Wrong enter count for! d1");
+ is(d2leaveCount, 1, "Wrong leave count for! d2");
+ is(d3enterCount, 1, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d3, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 3, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 4, "Wrong mouse enterLeave count for!");
+
+ is(d3leaveCount, 1, "Wrong leave count for! d3");
+ is(d1leaveCount, 1, "Wrong leave count for! d1");
+ is(d1enterCount, 2, "Wrong enter count for! d1");
+ is(d3enterCount, 2, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d2, cwu, 3), 0);
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 5, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 6, "Wrong mouse enterLeave count for!");
+
+ is(d1leaveCount, 2, "Wrong leave count for! d1");
+ is(d2enterCount, 2, "Wrong enter count for! d2");
+ is(d3leaveCount, 2, "Wrong leave count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 7, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 8, "Wrong mouse enterLeave count for!");
+
+ is(d2leaveCount, 3, "Wrong leave count for! d2");
+ is(d1enterCount, 4, "Wrong enter count for! d1");
+
+ // Test for pointer buttons when it generated from mousemove event
+ d1.onpointermove = function(e) {
+ is(e.buttons, 0, "Buttons must be 0 on pointer generated from mousemove");
+ is(e.button, -1, "Button must be -1 on pointer generated from mousemove when no buttons pressed");
+ is(e.pointerType, "mouse", "Pointer type must be mouse");
+ };
+ cwu.sendMouseEvent("mousemove", 4, 4, 0, 0, 0, false, 0, 0);
+
+ d1.onpointermove = function(e) {
+ is(e.buttons, 1, "Buttons must be 1 on pointer generated from touch event");
+ is(e.button, 0, "Button must be 0 on pointer generated from touch eventd");
+ is(e.pointerType, "touch", "Pointer type must be touch");
+ };
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 2), 0);
+
+ // Test for cancel trigger pointerOut (Touch Pointer must be at d1 now)
+ pointerCancelTriggered = 0;
+ var pointerOutTriggeredForCancelEvent = 0;
+ var pointerLeaveTriggeredForCancelEvent = 0;
+ d1.onpointerout = function(e) {
+ if (pointerOutTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerOutTriggeredForCancelEvent = 1;
+ };
+ d1.onpointerleave = function(e) {
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out must be dispatched bedore Pointer leave");
+ if (pointerLeaveTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerLeaveTriggeredForCancelEvent = 1;
+ }
+
+ sendTouchEvent(cwu, "touchcancel", getTouchEventForTarget(d1, cwu, 3), 0);
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out not dispatched on PointerCancel");
+ is(pointerLeaveTriggeredForCancelEvent, 1, "Pointer Leave not dispatched on PointerCancel");
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ setTimeout(function() {
+ window.parent.postMessage("SimpleTest.finish();", "*");
+ }, 0);
+ }, 0);
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true],
+ ]
+ }, runTests);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="d0">
+Test divs --
+<div id="d1">t</div><div id="d2">t</div><div id="d3">t</div>
+--
+</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug976963_inner.html b/layout/base/tests/bug976963_inner.html
new file mode 100644
index 000000000..2c55fbccd
--- /dev/null
+++ b/layout/base/tests/bug976963_inner.html
@@ -0,0 +1,241 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=976963
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 976963</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ div#listener {
+ background: yellow;
+ position: absolute;
+ top: -100px;
+ }
+ div#middler {
+ background: yellow;
+ margin: 10px;
+ }
+ div#target {
+ background: yellow;
+ }
+ </style>
+ <script type="application/javascript">
+ /** Test for Bug 976963 **/
+ var All_Pointer_Events = ["pointerover", "pointerenter",
+ "pointermove",
+ "pointerdown", "pointerup",
+ "pointerout", "pointerleave",
+ "pointercancel",
+ "gotpointercapture", "lostpointercapture"];
+
+ function on_event(object, event, callback) {
+ object.addEventListener(event, callback, false);
+ }
+ function ok(check, msg) {
+ parent.ok(check, msg);
+ }
+ function is(a, b, msg) {
+ parent.is(a, b, msg);
+ }
+
+ var listener = undefined;
+ var middler = undefined;
+ var target = undefined;
+
+ var test_ListenerGotCapture = 0;
+ var test_ListenerUnwanted = 0;
+ var test_ListenerLostCapture = 0;
+ var test_ListenerAfterCapture = 0;
+ var test_MiddlerGotCapture = 0;
+ var test_MiddlerOver = 0;
+ var test_MiddlerLeave = 0;
+ var test_MiddlerUp = 0;
+ var test_MiddlerLostCapture = 0;
+ var test_TargetDown = 0;
+ var test_TargetUnwanted = 0;
+ var test_TargetUp = 0;
+
+ var captured_event = undefined;
+ var f_gotPointerCapture = false;
+ var f_lostPointerCapture = false;
+ var f_gotMiddlerPointerCapture = false;
+
+ function listenerEventHandler(event) {
+ logger("Listener: " + event.type + ". Captured_event: " + captured_event);
+ if(test_ListenerLostCapture)
+ test_ListenerAfterCapture++;
+ if (event.type == "gotpointercapture") {
+ f_gotPointerCapture = true;
+ test_ListenerGotCapture++;
+ }
+ else if (event.type == "lostpointercapture") {
+ f_lostPointerCapture = true;
+ f_gotPointerCapture = false;
+ test_ListenerLostCapture++;
+ }
+ else if (event.type == "pointermove") {
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Listener: equals pointerId for lostpointercapture event");
+ if (f_gotPointerCapture) {
+ // on first event received for capture, release capture
+ logger("Listener call release");
+ ok(!!listener, "Listener should be live!");
+ ok(typeof(listener.releasePointerCapture) == "function", "Listener should have a function releasePointerCapture");
+ listener.releasePointerCapture(event.pointerId);
+ }
+ else {
+ logger("Listener.ASSERT: " + event.type);
+ test_ListenerUnwanted++;
+ // if any other events are received after releaseCapture, then the test fails
+ ok(false, event.target.id + "-" + event.type + " should be handled by target element handler");
+ }
+ }
+ else {
+ test_ListenerUnwanted++;
+ logger("Listener.ASSERT: " + event.type);
+ ok(false, event.type + "should be never handled by listener");
+ }
+ }
+
+ function middlerEventHandler(event) {
+ logger("Middler: " + event.type + ". Captured_event: " + captured_event);
+ if (event.type == "gotpointercapture") {
+ test_MiddlerGotCapture++;
+ f_gotMiddlerPointerCapture = true;
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Middler: equals pointerId for gotpointercapture event");
+ }
+ else if (event.type == "pointerover") {
+ test_MiddlerOver++;
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Middler: equals pointerId for pointerover event");
+ }
+ else if (event.type == "pointerleave") {
+ test_MiddlerLeave++;
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Middler: equals pointerId for pointerleave event");
+ ok(!!listener, "Listener should be live!");
+ ok(typeof(listener.setPointerCapture) == "function", "Listener should have a function setPointerCapture");
+ listener.setPointerCapture(event.pointerId);
+ }
+ else if (event.type == "lostpointercapture") {
+ test_MiddlerLostCapture++;
+ f_gotMiddlerPointerCapture = false;
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Middler: equals pointerId for lostpointercapture event");
+ }
+ else if (event.type == "pointerup" ) {
+ test_MiddlerUp++;
+ }
+ }
+
+ function targetEventHandler(event) {
+ logger("Target: " + event.type + ". Captured_event: " + captured_event);
+ if (f_gotPointerCapture || f_gotMiddlerPointerCapture) {
+ if (event.type != "pointerout" && event.type != "pointerleave") {
+ logger("Target.ASSERT: " + event.type + " " + event.pointerId);
+ test_TargetUnwanted++;
+ ok(false, "The Target element should not have received any events while capture is active. Event recieved:" + event.type + ". ");
+ }
+ }
+ if (event.type == "pointerdown") {
+ logger("Target.pointerdown 1: " + captured_event);
+ test_TargetDown++;
+ captured_event = event;
+ ok(!!middler, "Middler should be live!");
+ ok(typeof(middler.setPointerCapture) == "function", "Middler should have a function setPointerCapture");
+ middler.setPointerCapture(event.pointerId);
+ logger("Target.pointerdown 2: " + captured_event);
+ }
+ else if (event.type == "pointerup") {
+ ok(f_lostPointerCapture, "Target should have received pointerup");
+ ok(captured_event && captured_event.pointerId == event.pointerId, "Target: equals pointerId for lostpointercapture event");
+ test_TargetUp++; // complete test
+ }
+ }
+
+ function colorerHandler(event) {
+ if(event.type == "pointerover")
+ event.target.style.background = "red";
+ else if(event.type == "pointerout")
+ event.target.style.background = "yellow";
+ }
+
+ function setEventHandlers() {
+ listener = document.getElementById("listener");
+ middler = document.getElementById("middler");
+ target = document.getElementById("target");
+ target.style["touchAction"] = "none";
+
+ // target and listener - handle all events
+ for (var i = 0; i < All_Pointer_Events.length; i++) {
+ on_event(target, All_Pointer_Events[i], targetEventHandler);
+ on_event(listener, All_Pointer_Events[i], listenerEventHandler);
+ on_event(middler, All_Pointer_Events[i], middlerEventHandler);
+ on_event(target, All_Pointer_Events[i], colorerHandler);
+ on_event(middler, All_Pointer_Events[i], colorerHandler);
+ }
+ }
+
+ function prepareTest() {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, executeTest);
+ }
+
+ function executeTest()
+ {
+ logger("executeTest");
+ setEventHandlers();
+ document.body.offsetLeft;
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointermove"});
+ synthesizePointer(middler, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(middler, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+
+ function finishTest() {
+ setTimeout(function() {
+ is(test_ListenerGotCapture, 1, "Listener should receive gotpointercapture event");
+ is(test_ListenerUnwanted, 0, "Listener should not receive any unwanted events");
+ is(test_ListenerLostCapture, 1, "Listener should receive lostpointercapture event");
+ is(test_ListenerAfterCapture, 0, "Listener should not receive any events after release pointer capture");
+ is(test_MiddlerGotCapture, 1, "Middler should receive gotpointercapture event");
+ is(test_MiddlerOver, 1, "Middler should receive pointerover event");
+ is(test_MiddlerLeave, 1, "Middler should receive pointerleave event");
+ is(test_MiddlerUp, 0, "Middler should not receive pointerup event");
+ is(test_MiddlerLostCapture, 1, "Middler should receive lostpointercapture event");
+ is(test_TargetDown, 1, "Target should receive pointerdown event");
+ is(test_TargetUnwanted, 0, "Target should not receive any event while pointer capture is active");
+ is(test_TargetUp, 1, "Target should receive pointerup event");
+ logger("finishTest");
+ parent.finishTest();
+ }, 1000);
+ }
+
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=976963">Mozilla Bug 976963</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="middler">div id=middler</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_1.html b/layout/base/tests/bug977003_inner_1.html
new file mode 100644
index 000000000..c4c7f2d84
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_1.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094913
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098139
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1094913, 1098139</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target{ background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var test_send_got = 0;
+ var test_got_async = 0;
+ var test_got_type = "";
+ var test_got_primary = false;
+ var test_send_lost = 0;
+ var test_lost_async = 0;
+ var test_lost_type = "";
+ var test_lost_primary = false;
+
+ function DownHandler(event) {
+ logger("Receive event: " + event.type);
+ logger("Send setPointerCapture to target");
+ target.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_send_got++;
+ }
+ function GotPCHandler(event) {
+ logger("Receive event: " + event.type + "(" + event.pointerType + ")");
+ if(test_send_got)
+ test_got_async++;
+ test_got_type = event.pointerType;
+ test_got_primary = event.isPrimary;
+ logger("Send releasePointerCapture from target");
+ target.releasePointerCapture(event.pointerId);
+ logger("releasePointerCapture was executed");
+ test_send_lost++;
+ }
+ function LostPCHandler(event) {
+ logger("Received event: " + event.type + "(" + event.pointerType + ")");
+ if(test_send_lost)
+ test_lost_async++;
+ test_lost_type = event.pointerType;
+ test_lost_primary = event.isPrimary;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ target.addEventListener("pointerdown", DownHandler, false);
+ target.addEventListener("gotpointercapture", GotPCHandler, false);
+ target.addEventListener("lostpointercapture", LostPCHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_send_got, 1, "Part 1: gotpointercapture event should be sent once");
+ parent.is(test_got_async, 1, "Part 1: gotpointercapture event should be asynchronous");
+ parent.is(test_got_type, "mouse", "Part 1: gotpointercapture event should have pointerType mouse");
+ parent.is(test_got_primary, true, "Part 1: gotpointercapture event should have isPrimary as true");
+ parent.is(test_send_lost, 1, "Part 1: lostpointercapture event should be sent once");
+ parent.is(test_lost_async, 1, "Part 1: lostpointercapture event should be asynchronous");
+ parent.is(test_lost_type, "mouse", "Part 1: lostpointercapture event should have pointerType mouse");
+ parent.is(test_lost_primary, true, "Part 1: lostpointercapture event should have isPrimary as true");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 1</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094913">Mozilla Bug 1094913</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098139">Mozilla Bug 1098139</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_2.html b/layout/base/tests/bug977003_inner_2.html
new file mode 100644
index 000000000..0282bd01c
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_2.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ logger("set/release was executed");
+ test_down_got = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 2: pointerdown event should be received by target");
+ parent.is(test_listener, false, "Part 2: listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 2</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_3.html b/layout/base/tests/bug977003_inner_3.html
new file mode 100644
index 000000000..f332f8a43
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_3.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #mediator, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_mediator_got = false;
+ var test_mediator_lost = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send pointerCapture to Mediator");
+ mediator.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_got = true;
+ }
+ function MediatorGotPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ logger("Try send setPointerCapture on listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Try send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ test_mediator_got = true;
+ }
+ function MediatorLostPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ test_mediator_lost = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ mediator.addEventListener("gotpointercapture", MediatorGotPCHandler, false);
+ mediator.addEventListener("lostpointercapture", MediatorLostPCHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointermove"});
+ synthesizePointer(target, rect.width/4, rect.height/4, {type: "pointermove"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 3: pointerdown event should be received");
+ parent.is(test_mediator_got, true, "Part 3: gotpointercapture event should be received by Mediator");
+ parent.is(test_mediator_lost, true, "Part 3: lostpointercapture event should be received by Mediator");
+ parent.is(test_listener, false, "Part 3: listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 3</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_4.html b/layout/base/tests/bug977003_inner_4.html
new file mode 100644
index 000000000..be0865cc4
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_4.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #mediator, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_mediator_got = false;
+ var test_mediator_lost = false;
+ var test_listener_got = false;
+ var test_listener_lost = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send pointerCapture to Mediator");
+ mediator.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_got = true;
+ }
+ function MediatorGotPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ logger("Try send setPointerCapture on listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Try send releasePointerCapture from Mediator");
+ mediator.releasePointerCapture(event.pointerId);
+ test_mediator_got = true;
+ }
+ function MediatorLostPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ test_mediator_lost = true;
+ }
+ function ListenerGotHandler(event) {
+ test_listener_got = true;
+ }
+ function ListenerLostHandler(event) {
+ test_listener_lost = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ mediator.addEventListener("gotpointercapture", MediatorGotPCHandler, false);
+ mediator.addEventListener("lostpointercapture", MediatorLostPCHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerGotHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerLostHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointermove"});
+ synthesizePointer(target, rect.width/4, rect.height/4, {type: "pointermove"});
+ synthesizePointer(target, rect.width/4, rect.height/4, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 4: pointerdown event should be received");
+ parent.is(test_mediator_got, true, "Part 4: gotpointercapture event should be received by Mediator");
+ parent.is(test_mediator_lost, true, "Part 4: lostpointercapture event should be received by Mediator");
+ parent.is(test_listener_got, true, "Part 4: gotpointercapture event should be received by listener");
+ parent.is(test_listener_lost, true, "Part 4: lostpointercapture event should be received by listener");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 4</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_5.html b/layout/base/tests/bug977003_inner_5.html
new file mode 100644
index 000000000..70fc5ba40
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_5.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1073563
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094913
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098139
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1073563, 1094913, 1098139</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_down_target = false;
+ var test_got_listener = false;
+ var test_got_type = "";
+ var test_got_primary = false;
+ var test_lost_listener = false;
+ var test_lost_type = "";
+ var test_move_listener = false;
+ var test_listener = false;
+ var test_lost_primary = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_target = true;
+ }
+ function ListenerGotPCHandler(event) {
+ logger("Receive event on Listener: " + event.type + "(" + event.pointerType + ")");
+ listener.releasePointerCapture(event.pointerId);
+ test_got_listener = true;
+ test_got_type = event.pointerType;
+ test_got_primary = event.isPrimary;
+ }
+ function ListenerLostPCHandler(event) {
+ logger("Receive event on Listener: " + event.type + "(" + event.pointerType + ")");
+ test_lost_listener = true;
+ test_lost_type = event.pointerType;
+ test_lost_primary = event.isPrimary;
+ }
+ function ListenerMoveHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_move_listener = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerGotPCHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerLostPCHandler, false);
+ listener.addEventListener("pointerover", ListenerHandler, false);
+ listener.addEventListener("pointermove", ListenerMoveHandler, false);
+ listener.addEventListener("pointerup", ListenerHandler, false);
+ listener.addEventListener("pointerout", ListenerHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown", isPrimary: true, inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove", isPrimary: true, inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup", isPrimary: true, inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_target, true, "Part 5: pointerdown event should be received by target");
+ parent.is(test_got_listener, true, "Part 5: listener should receive gotpointercapture event");
+ parent.is(test_got_type, "touch", "Part 5: gotpointercapture event should have pointerType touch");
+ parent.is(test_got_primary, true, "Part 5: gotpointercapture event should have isPrimary as true");
+ parent.is(test_lost_listener, true, "Part 5: listener should receive lostpointercapture event");
+ parent.is(test_lost_type, "touch", "Part 5: lostpointercapture event should have pointerType touch");
+ parent.is(test_lost_primary, true, "Part 5: lostpointercapture event should have isPrimary as true");
+ parent.is(test_move_listener, true, "Part 5: gotpointercapture should be triggered by pointermove");
+ parent.is(test_listener, false, "Part 5: listener should not receive any other events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 5</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1073563">Mozilla Bug 1073563</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094913">Mozilla Bug 1094913</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098139">Mozilla Bug 1098139</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_6.html b/layout/base/tests/bug977003_inner_6.html
new file mode 100644
index 000000000..12424b1f2
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_6.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1073563
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1073563</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_move = false;
+ var test_listener = false;
+ var receive_lostpointercapture = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("set/release was executed");
+ test_target = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ if("gotpointercapture" == event.type) {
+ logger("Send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ } else if(event.type == "lostpointercapture") {
+ // Set/release pointer capture in the event listeners of got/lostpointercapture won't take effect immediately
+ parent.is(receive_lostpointercapture, false, "Part 6: listener should receive only one lostpointercapture");
+ if (!receive_lostpointercapture) {
+ receive_lostpointercapture = true;
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ }
+ } else if(event.type == "pointermove") {
+ test_move = true;
+ } else {
+ test_listener = true;
+ }
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ parent.turnOnPointerEvents(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler, false);
+ listener.addEventListener("gotpointercapture", ListenerHandler, false);
+ listener.addEventListener("pointerdown", ListenerHandler, false);
+ listener.addEventListener("pointerover", ListenerHandler, false);
+ listener.addEventListener("pointermove", ListenerHandler, false);
+ listener.addEventListener("pointerup", ListenerHandler, false);
+ listener.addEventListener("pointerout", ListenerHandler, false);
+ listener.addEventListener("lostpointercapture", ListenerHandler, false);
+ var rect = target.getBoundingClientRect();
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerdown"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
+ synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointerup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "Part 6: pointerdown event should be received by target");
+ // PE level 2 defines that the pending pointer capture is processed when firing next pointer events.
+ // In this test case, pointer capture release is processed when firing pointermove
+ parent.is(test_move, true, "Part 6: gotpointercapture should be triggered by pointermove");
+ parent.is(test_listener, false, "Part 6: no other pointerevents should be fired before gotpointercapture");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 6</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1073563">Mozilla Bug 1073563</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug989012-1-ref.html b/layout/base/tests/bug989012-1-ref.html
new file mode 100644
index 000000000..f1b922b11
--- /dev/null
+++ b/layout/base/tests/bug989012-1-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img alt="IMAGE">bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-1.html b/layout/base/tests/bug989012-1.html
new file mode 100644
index 000000000..5bc9a2061
--- /dev/null
+++ b/layout/base/tests/bug989012-1.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img alt="IMAGE">bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-2-ref.html b/layout/base/tests/bug989012-2-ref.html
new file mode 100644
index 000000000..246ad5307
--- /dev/null
+++ b/layout/base/tests/bug989012-2-ref.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ span:before {
+ content: "IMAGE";
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<span></span>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-2.html b/layout/base/tests/bug989012-2.html
new file mode 100644
index 000000000..32b43418e
--- /dev/null
+++ b/layout/base/tests/bug989012-2.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ span:before {
+ content: "IMAGE";
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<span></span>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-3-ref.html b/layout/base/tests/bug989012-3-ref.html
new file mode 100644
index 000000000..29b08b991
--- /dev/null
+++ b/layout/base/tests/bug989012-3-ref.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ img {
+ border: solid 1px red;
+ mid-width: 1em;
+ display: inline-block;
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-3.html b/layout/base/tests/bug989012-3.html
new file mode 100644
index 000000000..6510e7d62
--- /dev/null
+++ b/layout/base/tests/bug989012-3.html
@@ -0,0 +1,31 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ img {
+ border: solid 1px red;
+ mid-width: 1em;
+ display: inline-block;
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/chrome/animated.gif b/layout/base/tests/chrome/animated.gif
new file mode 100644
index 000000000..b2895487b
--- /dev/null
+++ b/layout/base/tests/chrome/animated.gif
Binary files differ
diff --git a/layout/base/tests/chrome/blue-32x32.png b/layout/base/tests/chrome/blue-32x32.png
new file mode 100644
index 000000000..deefd19b2
--- /dev/null
+++ b/layout/base/tests/chrome/blue-32x32.png
Binary files differ
diff --git a/layout/base/tests/chrome/bug1041200_window.html b/layout/base/tests/chrome/bug1041200_window.html
new file mode 100644
index 000000000..e098e1d92
--- /dev/null
+++ b/layout/base/tests/chrome/bug1041200_window.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1041200</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+</head>
+<body>
+<iframe style="width:700px; height:500px; margin-top:200px;" id="ourFrame"></iframe>
+<script>
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+var ok = window.opener.wrappedJSObject.ok;
+var info = window.opener.wrappedJSObject.info;
+
+var viewer =
+ SpecialPowers.wrap(ourFrame).contentWindow
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .contentViewer;
+viewer.fullZoom = 2;
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ window.waitForAllPaintsFlushed(function () {
+ // Supply random key to ensure load actually happens
+ ourFrame.src = "data:text/html,<body onload='parent.childLoaded()' style='background:lime'><p>Hello<p>Hello<p>Hello<p>Hello<p>Hello<p>" + Math.random();
+ }, document.getElementById("ourFrame").contentDocument);
+};
+
+window.childLoaded = function() {
+ setTimeout(function() {
+ window.waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+ ok(x2 - x1 >= 700 && y2 - y1 >= 500,
+ "expected to see invalidate of entire frame, got " + [x1,y1,x2,y2].join(','));
+ SimpleTest.finish();
+ window.close();
+ });
+ }, 0);
+};
+</script>
+
diff --git a/layout/base/tests/chrome/bug495648.rdf b/layout/base/tests/chrome/bug495648.rdf
new file mode 100644
index 000000000..b7045aa70
--- /dev/null
+++ b/layout/base/tests/chrome/bug495648.rdf
@@ -0,0 +1,214 @@
+<?xml version="1.0"?>
+<RDF:RDF xmlns:NS1="http://sitedelta.schierla.de/SD-rdf#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=19&amp;btnG=Suche&amp;meta="
+ NC:name="19 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Bag RDF:about="urn:root:bag">
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=1&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=2&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=3&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=4&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=5&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=6&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=7&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=8&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=9&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=10&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=11&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=12&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=13&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=14&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=15&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=16&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=17&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=18&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=19&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=20&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=21&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=22&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=23&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=24&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=25&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=26&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=27&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=28&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=29&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=30&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=31&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=32&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=33&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=34&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=35&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=36&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=37&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=38&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=39&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=40&amp;btnG=Suche&amp;meta="/>
+ </RDF:Bag>
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=2&amp;btnG=Suche&amp;meta="
+ NC:name="2 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=12&amp;btnG=Suche&amp;meta="
+ NC:name="12 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=5&amp;btnG=Suche&amp;meta="
+ NC:name="5 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=6&amp;btnG=Suche&amp;meta="
+ NC:name="6 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=22&amp;btnG=Suche&amp;meta="
+ NC:name="22 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=17&amp;btnG=Suche&amp;meta="
+ NC:name="17 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="urn:root">
+ <NC:links RDF:resource="urn:root:bag"/>
+ </RDF:Description>
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=8&amp;btnG=Suche&amp;meta="
+ NC:name="8 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=1&amp;btnG=Suche&amp;meta="
+ NC:name="1 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=16&amp;btnG=Suche&amp;meta="
+ NC:name="16 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=21&amp;btnG=Suche&amp;meta="
+ NC:name="21 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=20&amp;btnG=Suche&amp;meta="
+ NC:name="20 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=9&amp;btnG=Suche&amp;meta="
+ NC:name="9 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=11&amp;btnG=Suche&amp;meta="
+ NC:name="11 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=14&amp;btnG=Suche&amp;meta="
+ NC:name="14 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=23&amp;btnG=Suche&amp;meta="
+ NC:name="23 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=24&amp;btnG=Suche&amp;meta="
+ NC:name="24 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=18&amp;btnG=Suche&amp;meta="
+ NC:name="18 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www0.pafnet.de/user/322033"
+ NC:name="pafnet - You2_xD"
+ NS1:nextScan="1243707104073"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=3&amp;btnG=Suche&amp;meta="
+ NC:name="3 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=7&amp;btnG=Suche&amp;meta="
+ NC:name="7 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=15&amp;btnG=Suche&amp;meta="
+ NC:name="15 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=4&amp;btnG=Suche&amp;meta="
+ NC:name="4 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=13&amp;btnG=Suche&amp;meta="
+ NC:name="13 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=10&amp;btnG=Suche&amp;meta="
+ NC:name="10 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=25&amp;btnG=Suche&amp;meta="
+ NC:name="25 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=26&amp;btnG=Suche&amp;meta="
+ NC:name="26 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=27&amp;btnG=Suche&amp;meta="
+ NC:name="27 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=28&amp;btnG=Suche&amp;meta="
+ NC:name="28 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=29&amp;btnG=Suche&amp;meta="
+ NC:name="29 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=30&amp;btnG=Suche&amp;meta="
+ NC:name="30 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=31&amp;btnG=Suche&amp;meta="
+ NC:name="31 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=32&amp;btnG=Suche&amp;meta="
+ NC:name="32 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=33&amp;btnG=Suche&amp;meta="
+ NC:name="33 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=34&amp;btnG=Suche&amp;meta="
+ NC:name="34 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=35&amp;btnG=Suche&amp;meta="
+ NC:name="35 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=36&amp;btnG=Suche&amp;meta="
+ NC:name="36 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=37&amp;btnG=Suche&amp;meta="
+ NC:name="37 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=38&amp;btnG=Suche&amp;meta="
+ NC:name="38 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=39&amp;btnG=Suche&amp;meta="
+ NC:name="39 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=40&amp;btnG=Suche&amp;meta="
+ NC:name="40 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+</RDF:RDF>
diff --git a/layout/base/tests/chrome/bug551434_childframe.html b/layout/base/tests/chrome/bug551434_childframe.html
new file mode 100644
index 000000000..3d7bd6c13
--- /dev/null
+++ b/layout/base/tests/chrome/bug551434_childframe.html
@@ -0,0 +1,4 @@
+<script>
+var gKeyDownChild = 0, gKeyPressChild = 0, gKeyUpChild = 0;
+</script>
+<input id='i4' onkeydown="gKeyDownChild++" onkeypress="gKeyPressChild++" onkeyup="gKeyUpChild++; this.parentNode.removeChild(this);">
diff --git a/layout/base/tests/chrome/chrome.ini b/layout/base/tests/chrome/chrome.ini
new file mode 100644
index 000000000..2f6ca0ba8
--- /dev/null
+++ b/layout/base/tests/chrome/chrome.ini
@@ -0,0 +1,49 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ animated.gif
+ blue-32x32.png
+ bug495648.rdf
+ bug551434_childframe.html
+ chrome_content_integration_window.xul
+ chrome_over_plugin_window.xul
+ default_background_window.xul
+ dialog_with_positioning_window.xul
+ no_clip_iframe_subdoc.html
+ no_clip_iframe_window.xul
+ printpreview_bug396024_helper.xul
+ printpreview_bug482976_helper.xul
+ printpreview_helper.xul
+ file_bug1018265.xul
+
+[test_bug396367-1.html]
+[test_bug396367-2.html]
+[test_bug420499.xul]
+[test_bug458898.html]
+[test_bug495648.xul]
+[test_bug504311.xul]
+[test_bug514660.xul]
+[test_bug533845.xul]
+[test_bug551434.html]
+[test_bug708062.html]
+[test_bug812817.xul]
+[test_bug847890_paintFlashing.html]
+[test_bug1018265.xul]
+[test_bug1041200.xul]
+support-files=bug1041200_window.html
+[test_chrome_content_integration.xul]
+[test_chrome_over_plugin.xul]
+[test_default_background.xul]
+[test_dialog_with_positioning.html]
+tags = openwindow
+[test_fixed_bg_scrolling_repaints.html]
+[test_leaf_layers_partition_browser_window.xul]
+skip-if = (!debug) || (toolkit == "cocoa") || (os == "linux") # Disabled on Mac and Linux because of Bug 992311
+[test_no_clip_iframe.xul]
+[test_prerendered_transforms.html]
+[test_printpreview.xul]
+skip-if = os == "linux" && bits == 32 # Disabled on Linux32 for bug 1278957
+[test_printpreview_bug396024.xul]
+[test_printpreview_bug482976.xul]
+[test_scrolling_repaints.html]
+[test_will_change.html]
diff --git a/layout/base/tests/chrome/chrome_content_integration_window.xul b/layout/base/tests/chrome/chrome_content_integration_window.xul
new file mode 100644
index 000000000..23cd21e6e
--- /dev/null
+++ b/layout/base/tests/chrome/chrome_content_integration_window.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<window title="Content/chrome integration subwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()"
+ style="background:black; -moz-appearance:none;">
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <stack style="height:300px; width:200px;">
+ <!-- the bottom 100px is a strip of black that should be vixible through the content iframe -->
+ <vbox style="background:pink; border-bottom:100px solid black"/>
+ <!-- the middle 100px is a strip of black in the content iframe -->
+ <!-- the bottom 100px of the iframe is transparent, the top 100px is yellow -->
+ <iframe type="content" style="border:none;"
+ transparent="transparent"
+ src="data:text/html,&lt;div style='position:absolute;left:0;top:0;width:100%;height:100px;background:yellow;border-bottom:100px solid black'&gt;"/>
+ <!-- the top 100px is a strip of black above the content iframe -->
+ <vbox style="border-top:100px solid black;"/>
+ </stack>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "SpecialPowers" ];
+ for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ function runTests() {
+ var testCanvas = snapshotWindow(window);
+
+ var refCanvas = snapshotWindow(window);
+ var ctx = refCanvas.getContext('2d');
+ ctx.fillStyle = "black";
+ ctx.fillRect(0, 0, refCanvas.width, refCanvas.height);
+
+ var comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Rendering OK, got " + comparison[1] + ", expected " + comparison[2]);
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/chrome_over_plugin_window.xul b/layout/base/tests/chrome/chrome_over_plugin_window.xul
new file mode 100644
index 000000000..54c18fd56
--- /dev/null
+++ b/layout/base/tests/chrome/chrome_over_plugin_window.xul
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<window title="Content/chrome integration subwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()">\
+ <!-- We're mainly testing that a) translucent chrome elements cause the plugin to be clipped away and
+ b) translucent content elements do NOT cause the plugin to be clipped away -->
+ <stack style="height:100px; width:150px;">
+ <iframe type="content" style="border:none;" id="f"
+ src="data:text/html,&lt;embed id='e' type='application/x-test' wmode='window'
+ style='position:absolute;left:0;top:0;width:100px;height:100px'&gt;&lt;/embed&gt;
+ &lt;div style='position:absolute;left:0;top:80px;width:100px;height:10px;background:rgba(0,0,128,0.5)'&gt;&lt;/div&gt;
+ &lt;div style='position:absolute;left:0;top:90px;width:100px;height:10px;background:blue'&gt;&lt;/div&gt;
+ "/>
+ <vbox>
+ <vbox style="height:25px; background:yellow;"/> <!-- plugin should be covered here -->
+ <vbox style="height:25px; background:rgba(0,128,0,0.5);"/> <!-- plugin should be covered here -->
+ <vbox style="height:50px;"/> <!-- plugin should be visible here -->
+ </vbox>
+ </stack>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "todo" ];
+ for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ var plugin;
+ function waitForPaint() {
+ if (plugin.getPaintCount() < 1) {
+ setTimeout(waitForPaint, 0);
+ return;
+ }
+
+ if (plugin.hasWidget()) {
+ is(plugin.getClipRegionRectCount(), 1, "plugin clip rect count");
+ var left = plugin.getEdge(0);
+ var top = plugin.getEdge(1);
+ is(plugin.getClipRegionRectEdge(0,0) - left, 0, "plugin clip rect left");
+ // our two vboxes with backgrounds should cause the top of the plugin to be clipped
+ is(plugin.getClipRegionRectEdge(0,1) - top, 50, "plugin clip rect top");
+ is(plugin.getClipRegionRectEdge(0,2) - left, 100, "plugin clip rect right");
+ // of the two content DIVs, the first one should not cause the plugin to be clipped because
+ // it's transparent. The second one should cause the plugin to be clipped.
+ is(plugin.getClipRegionRectEdge(0,3) - top, 90, "plugin clip rect bottom");
+ } else {
+ todo(false, "Test only tests windowed plugins");
+ }
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }
+
+ function runTests() {
+ plugin = document.getElementById("f").contentDocument.getElementById("e").wrappedJSObject;
+ waitForPaint();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/default_background_window.xul b/layout/base/tests/chrome/default_background_window.xul
new file mode 100644
index 000000000..110dd8c15
--- /dev/null
+++ b/layout/base/tests/chrome/default_background_window.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <iframe type="content" id="f" src="about:blank" style="border:1px solid black;"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ function snapshot(win) {
+ var el = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ el.width = win.innerWidth;
+ el.height = win.innerHeight;
+
+ var ctx = el.getContext("2d");
+ ctx.drawWindow(win, 0, 0,
+ win.innerWidth, win.innerHeight,
+ "rgba(0,0,0,0)", 0);
+ return el;
+ }
+
+ var color = '#2468AC';
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setCharPref('browser.display.background_color', color);
+
+ function runTests() {
+ var f = document.getElementById("f");
+
+ var testCanvas = snapshot(f.contentWindow);
+ prefs.clearUserPref('browser.display.background_color');
+
+ var refCanvas = snapshot(f.contentWindow);
+ var ctx = refCanvas.getContext('2d');
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, refCanvas.width, refCanvas.height);
+
+ var comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Rendering OK, got " + comparison[1] + ", expected " + comparison[2]);
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/dialog_with_positioning_window.xul b/layout/base/tests/chrome/dialog_with_positioning_window.xul
new file mode 100644
index 000000000..6b94fefdf
--- /dev/null
+++ b/layout/base/tests/chrome/dialog_with_positioning_window.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTest, 0)">
+ <vbox>
+ <text value="powered by example.com" style="padding: 16px;"/>
+ </vbox>
+ <hbox id="t" style="position: fixed; right: 16px; bottom: 16px;">
+ <button label="OK"/>
+ </hbox>
+<script><![CDATA[
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+var is = window.opener.wrappedJSObject.is;
+var ok = window.opener.wrappedJSObject.ok;
+
+// We run this off a setTimeout from onload, because the XUL window
+// only does its intrinsic-height layout after the load event has
+// finished
+function runTest() {
+ var t = document.getElementById("t");
+ var tBottom = t.getBoundingClientRect().bottom;
+ is(tBottom, document.documentElement.getBoundingClientRect().bottom - 16,
+ "check fixed-pos element t bottom positioned correctly");
+ ok(tBottom < 200, "fixed-pos element t bottom must be sane, less than 200 (got " + tBottom + ")");
+ window.close();
+ SimpleTest.finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/file_bug1018265.xul b/layout/base/tests/chrome/file_bug1018265.xul
new file mode 100644
index 000000000..91944c5b4
--- /dev/null
+++ b/layout/base/tests/chrome/file_bug1018265.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1018265
+-->
+<window title="Mozilla Bug 1018265"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(run, 0);">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1018265 **/
+
+ var testcontent = null;
+
+ function run() {
+ testcontent = document.getElementById("testcontent");
+ shouldHaveTwoNonHiddenContentViewers();
+ testcontent.setAttribute("src", "foobarpage");
+ setTimeout(errorPageLoaded, 2500)
+ }
+
+ function errorPageLoaded() {
+ testcontent.addEventListener("pageshow", didGoBack, true);
+ setTimeout("testcontent.contentWindow.history.back();", 0);
+ }
+
+ function didGoBack(e) {
+ testcontent.removeEventListener("pageshow", didGoBack, true);
+ shouldHaveTwoNonHiddenContentViewers();
+ opener.done();
+ window.close();
+ }
+
+ function getContentViewer(win) {
+ return win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDocShell).contentViewer;
+ }
+
+ function shouldHaveTwoNonHiddenContentViewers() {
+ opener.is(getContentViewer(testcontent.contentWindow).isHidden, false, "Top level ContentViewer should not be hidden.");
+ opener.is(getContentViewer(testcontent.contentWindow.frames[0]).isHidden, false, " Iframe's ContentViewer should not be hidden.");
+ }
+ ]]>
+ </script>
+
+ <browser type="content" id="testcontent" flex="1" src="data:text/html,&lt;iframe&gt;&lt;/iframe&gt;"/>
+</window>
diff --git a/layout/base/tests/chrome/no_clip_iframe_subdoc.html b/layout/base/tests/chrome/no_clip_iframe_subdoc.html
new file mode 100644
index 000000000..b00b48be6
--- /dev/null
+++ b/layout/base/tests/chrome/no_clip_iframe_subdoc.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body style="margin:0; background:lime;">
+<div id="d" style="position:relative; top:-50px; width:150px; height:250px; background:yellow;"></div>
+<div id="p" style="margin-top:-50px; width:150px; height:50px;"></div>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/no_clip_iframe_window.xul b/layout/base/tests/chrome/no_clip_iframe_window.xul
new file mode 100644
index 000000000..960477e9e
--- /dev/null
+++ b/layout/base/tests/chrome/no_clip_iframe_window.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+
+ <div id="container" xmlns="http://www.w3.org/1999/xhtml" style="height:400px; overflow:auto; background:gray">
+ <div style="height:0">
+ <iframe type="content" id="f" src="no_clip_iframe_subdoc.html"
+ style="margin-top:50px; border:1px solid black; width:100px; height:100px;"/>
+ </div>
+ <div id="ref" style="background:gray;">
+ <div style="border:1px solid black; margin-top:50px; width:100px; height:100px;">
+ <div id="ref-d" style="background:lime; height:250px; width:150px;">
+ <div style="position:relative; top:-50px; width:150px; height:100%; background:yellow;"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ <vbox flex="1"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ];
+ for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var Ci = Components.interfaces;
+ var frame = document.getElementById("f");
+ var fl = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ is(fl.clipSubdocument, true, "clipSubdocument should default to true");
+ fl.clipSubdocument = false;
+ is(fl.clipSubdocument, false, "clipSubdocument should have been set to false");
+
+ function runTests() {
+ var ref = document.getElementById("ref");
+ frame.contentWindow.scrollTo(0,0);
+
+ ref.style.visibility = "hidden";
+ var testCanvas = snapshotWindow(window);
+ ref.style.visibility = "";
+ var refCanvas = snapshotWindow(window);
+ var comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Basic overflow drawing; got " + comparison[1] + ", expected " + comparison[2]);
+
+ document.getElementById("container").style.height = "200px";
+ ref.style.visibility = "hidden";
+ testCanvas = snapshotWindow(window);
+ ref.style.visibility = "";
+ refCanvas = snapshotWindow(window);
+ comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Drawing with vertical scrollbar to show overflow area computation; got " +
+ comparison[1] + ", expected " + comparison[2]);
+
+ frame.contentDocument.getElementById("d").style.height = "350px";
+ document.getElementById("ref-d").style.height = "350px";
+ ref.style.visibility = "hidden";
+ testCanvas = snapshotWindow(window);
+ ref.style.visibility = "";
+ refCanvas = snapshotWindow(window);
+ comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "testing dynamic overflow area change affecting scrollbar; got " +
+ comparison[1] + ", expected " + comparison[2]);
+
+ // Now do invalidation tests
+ ref.style.visibility = "hidden";
+ document.getElementById("container").style.height = "400px";
+ waitForAllPaintsFlushed(function() {
+ frame.contentWindow.scrollTo(0,80);
+ waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+ ok(x1 <= 1 && x2 >= 151 && y1 <= 0 && y2 >= 400,
+ "Entire scrolled region is painted: " + x1 + "," + y1 + "," + x2 + "," + y2);
+ frame.contentDocument.getElementById("p").style.background = "cyan";
+ waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+ ok(x1 <= 1 && x2 >= 151 && y1 <= 271 && y2 >= 320,
+ "Entire updated region is painted: " + x1 + "," + y1 + "," + x2 + "," + y2);
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }, frame.contentDocument);
+ });
+ });
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_bug396024_helper.xul b/layout/base/tests/chrome/printpreview_bug396024_helper.xul
new file mode 100644
index 000000000..8fe2609f5
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug396024_helper.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396024
+-->
+<window title="Mozilla Bug 396024" onload="run()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<iframe id="i" src="about:blank" type="content"></iframe>
+<iframe src="about:blank" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+var is = window.opener.wrappedJSObject.is;
+var ok = window.opener.wrappedJSObject.ok;
+var todo = window.opener.wrappedJSObject.todo;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+var gWbp;
+function printpreview() {
+ gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ var listener = {
+ onLocationChange: function(webProgress, request, location, flags) { },
+ onProgressChange: function(webProgress, request, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress) { },
+ onSecurityChange: function(webProgress, request, state) { },
+ onStateChange: function(webProgress, request, stateFlags, status) { },
+ onStatusChange: function(webProgress, request, status, message) { },
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ }
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref('print.show_print_progress', false);
+ //XXX I would have thought this would work, instead I'm forced to use prefs service
+ gWbp.globalPrintSettings.showPrintProgress = false;
+ gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+ prefs.clearUserPref('print.show_print_progress');
+}
+
+function exitprintpreview() {
+ window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint).exitPrintPreview();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+function run()
+{
+/** Test for Bug 396024 **/
+ var printService = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+
+ try {
+ Components.classes["@mozilla.org/gfx/printerenumerator;1"]
+ .getService(Components.interfaces.nsIPrinterEnumerator);
+ } catch(e) {
+ todo(false, "Test skipped on MacOSX, as the print preview code doesn't work there");
+ finish();
+ return;
+ }
+
+ if (printService.defaultPrinterName != '') {
+ printpreview();
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore1");
+ printpreview();
+ setTimeout(run2, 0)
+ } else {
+ todo(false, "No printer seems installed on this machine, that is necessary for this test");
+ finish();
+ }
+}
+
+function run2() {
+ var loadhandler = function() {
+ document.getElementById("i").removeEventListener("load", arguments.callee, true);
+ setTimeout(run3, 0);
+ };
+ document.getElementById("i").addEventListener("load", loadhandler, true);
+ window.frames[0].location.reload();
+}
+
+function run3() {
+ gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ setTimeout(run4, 0);
+}
+
+function run4() {
+ var i = document.getElementById("i");
+ var x = i.parentNode.removeChild(i);
+ var loadhandler = function() {
+ document.getElementById("i").removeEventListener("load", loadhandler, true);
+ setTimeout(run5, 0);
+ };
+ x.addEventListener("load", loadhandler, true);
+ document.documentElement.getBoundingClientRect();
+ document.documentElement.appendChild(x);
+ gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore2");
+}
+
+function run5() {
+ //XXX this shouldn't be necessary, see bug 405555
+ printpreview();
+ exitprintpreview();
+ finish(); //should not have crashed after all of this
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_bug482976_helper.xul b/layout/base/tests/chrome/printpreview_bug482976_helper.xul
new file mode 100644
index 000000000..34c2d658e
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug482976_helper.xul
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482976
+-->
+<window title="Mozilla Bug 482976" onload="run1()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<iframe src="about:blank" type="content"></iframe>
+<iframe src="about:blank" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+var is = window.opener.wrappedJSObject.is;
+var ok = window.opener.wrappedJSObject.ok;
+var todo = window.opener.wrappedJSObject.todo;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+var gWbp;
+function printpreview() {
+ gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ var listener = {
+ onLocationChange: function(webProgress, request, location, flags) { },
+ onProgressChange: function(webProgress, request, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress) { },
+ onSecurityChange: function(webProgress, request, state) { },
+ onStateChange: function(webProgress, request, stateFlags, status) { },
+ onStatusChange: function(webProgress, request, status, message) { },
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIWebProgessListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ }
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref('print.show_print_progress', false);
+ //XXX I would have thought this would work, instead I'm forced to use prefs service
+ gWbp.globalPrintSettings.showPrintProgress = false;
+ gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+ prefs.clearUserPref('print.show_print_progress');
+}
+
+function exitprintpreview() {
+ window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint).exitPrintPreview();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+function run1()
+{
+/** Test for Bug 482976 **/
+ var printService = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+
+ try {
+ Components.classes["@mozilla.org/gfx/printerenumerator;1"]
+ .getService(Components.interfaces.nsIPrinterEnumerator);
+ } catch(e) {
+ todo(false, "Test skipped on MacOSX, as the print preview code doesn't work there");
+ finish();
+ return;
+ }
+
+ if (printService.defaultPrinterName != '') {
+ printpreview();
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore");
+ } else {
+ todo(false, "No printer seems installed on this machine, that is necessary for this test");
+ }
+ finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_helper.xul b/layout/base/tests/chrome/printpreview_helper.xul
new file mode 100644
index 000000000..96d835b9a
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_helper.xul
@@ -0,0 +1,274 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window onload="runTests()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<iframe height="200" width="600" type="content"></iframe>
+<iframe height="200" width="600" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+var is = window.opener.wrappedJSObject.is;
+var isnot = window.opener.wrappedJSObject.isnot;
+var ok = window.opener.wrappedJSObject.ok;
+var todo = window.opener.wrappedJSObject.todo;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+var gWbp;
+var ctx1;
+var ctx2;
+var counter = 0;
+
+var file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("TmpD", Components.interfaces.nsILocalFile);
+filePath = file.path;
+
+function printpreview() {
+ gWbp = window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ var listener = {
+ onLocationChange: function(webProgress, request, location, flags) { },
+ onProgressChange: function(webProgress, request, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress) { },
+ onSecurityChange: function(webProgress, request, state) { },
+ onStateChange: function(webProgress, request, stateFlags, status) { },
+ onStatusChange: function(webProgress, request, status, message) { },
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ }
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref('print.show_print_progress', false);
+ //XXX I would have thought this would work, instead I'm forced to use prefs service
+ gWbp.globalPrintSettings.showPrintProgress = false;
+ var before = 0;
+ var after = 0;
+ function beforeprint() { ++before; }
+ function afterprint() { ++after; }
+ window.frames[0].addEventListener("beforeprint", beforeprint, true);
+ window.frames[0].addEventListener("afterprint", afterprint, true);
+ gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+ is(before, 1, "Should have called beforeprint listener!");
+ is(after, 1, "Should have called afterprint listener!");
+ window.frames[0].removeEventListener("beforeprint", beforeprint, true);
+ window.frames[0].removeEventListener("afterprint", afterprint, true);
+ prefs.clearUserPref('print.show_print_progress');
+}
+
+function exitprintpreview() {
+ window.frames[1].QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint).exitPrintPreview();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+function runTests()
+{
+ var printService = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+
+ try {
+ Components.classes["@mozilla.org/gfx/printerenumerator;1"]
+ .getService(Components.interfaces.nsIPrinterEnumerator);
+ } catch(e) {
+ todo(false, "Test skipped on MacOSX, as the print preview code doesn't work there");
+ finish();
+ return;
+ }
+
+ if (printService.defaultPrinterName != '') {
+ startTest1();
+ } else {
+ todo(false, "No printer seems installed on this machine, that is necessary for this test");
+ finish();
+ }
+}
+
+function compareCanvases() {
+ return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .compareCanvases(document.getElementsByTagName("canvas")[0],
+ document.getElementsByTagName("canvas")[1],
+ {}) == 0;
+}
+
+function addHTMLContent(parent) {
+ var n = parent.ownerDocument.createElement("div");
+ parent.appendChild(n);
+ var s = "<iframe width='500' height='40' src='data:text/plain,ThisIsAnIframeCreatedDuringPrintPreview'></iframe>";
+ s += "<table>";
+ for (var i = 1; i < 501; ++i) {
+ s += "<tr><td>Cell A" + i + "</td><td>Cell B" + i + "</td><td>Cell C" + i + "</td></tr>";
+ }
+ s += "</table>";
+ n.innerHTML = s;
+}
+
+function startTest1() {
+ ctx1 = document.getElementsByTagName("canvas")[0].getContext("2d");
+ ctx2 = document.getElementsByTagName("canvas")[1].getContext("2d");
+ window.frames[0].document.body.innerHTML = "<div> </div><div>" + counter + " timers</div><div> </div>";
+
+ // Note this timeout is needed so that we can check that timers run
+ // after print preview, but not during it.
+ window.frames[0].wrappedJSObject.counter = counter;
+ window.frames[0].counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
+ "window.setTimeout(counterTimeout, 0);";
+ window.frames[0].setTimeout(window.frames[0].counterTimeout, 0);
+ window.frames[0].document.body.firstChild.innerHTML = "Print preview";
+
+ printpreview();
+ ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+ window.frames[0].document.body.firstChild.innerHTML = "Galley presentation";
+
+ // Add some elements.
+ addHTMLContent(window.frames[0].document.body.lastChild);
+ // Delete them.
+ window.frames[0].document.body.lastChild.innerHTML = "";
+ // And readd.
+ addHTMLContent(window.frames[0].document.body.lastChild);
+
+ setTimeout(finalizeTest1, 1000);
+}
+
+function finalizeTest1() {
+ ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+ exitprintpreview();
+ ok(compareCanvases(), "Canvas should be the same!");
+ counter = window.frames[0].counter;
+ // This timeout is needed so that we can check that timers do run after
+ // print preview.
+ setTimeout(runTest2, 1000);
+}
+
+function runTest2() {
+ isnot(window.frames[0].document.body.firstChild.nextSibling.textContent, "0 timers", "Timers should have run!");
+ isnot(window.frames[0].counter, 0, "Timers should have run!");
+ counter = window.frames[0].counter;
+ window.frames[0].counterTimeout = "";
+ setTimeout(runTest3, 0);
+}
+
+var elementIndex = 0;
+var compareEmptyElement = true;
+var emptyFormElements =
+ ["<input type='text'>",
+ "<input type='password'>",
+ "<input type='file'>",
+ "<input type='button'>",
+ "<input type='submit'>",
+ "<input type='reset'>",
+ "<input type='checkbox'>",
+ "<input type='radio'>",
+ "<select></select>",
+ "<select size='5'></select>",
+ "<textarea></textarea>"];
+
+var formElements =
+ ["<input type='text' value='text'>",
+ "<input type='password' value='password'>",
+ "<input type='file' value='" + filePath + "'>",
+ "<input type='button' value='button'>",
+ "<input type='submit' value='submit button'>",
+ "<input type='reset' value='reset button'>",
+ "<input type='checkbox' checked>",
+ "<input type='radio' checked>",
+ "<select><option>option1</option></select>",
+ "<select size='5'><option>1</option><option>2</option><option>3</option></select>",
+ "<textarea value='textarea'>textarea</textarea>"];
+
+function runTest3() {
+ if (compareEmptyElement) {
+ var currentIndex = elementIndex;
+ ++elementIndex;
+ if (elementIndex >= emptyFormElements.length) {
+ elementIndex = 0;
+ compareEmptyElement = false;
+ }
+ compareFormElementPrint(emptyFormElements[currentIndex], emptyFormElements[currentIndex], true);
+ return;
+ } else if (elementIndex < emptyFormElements.length) {
+ var currentIndex = elementIndex;
+ ++elementIndex;
+ compareFormElementPrint(emptyFormElements[currentIndex], formElements[currentIndex], false);
+ return;
+ }
+
+ setTimeout(runTest4, 0)
+}
+
+function compareFormElementPrint(el1, el2, equals) {
+ window.frames[0].document.body.innerHTML = el1;
+ window.frames[0].document.body.firstChild.value =
+ window.frames[0].document.body.firstChild.getAttribute('value');
+ printpreview();
+ ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+ exitprintpreview();
+ window.frames[0].document.body.innerHTML = el2;
+ window.frames[0].document.body.firstChild.value =
+ window.frames[0].document.body.firstChild.getAttribute('value');
+ printpreview();
+ ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+ exitprintpreview();
+ is(compareCanvases(), equals,
+ "Comparing print preview didn't succeed [" + el1 + " : " + el2 + "]");
+ setTimeout(runTest3, 100);
+}
+
+// This is a crash test for bug 539060.
+function runTest4() {
+ window.frames[0].document.body.innerHTML =
+ "<iframe style='display: none;' src='data:text/html,<iframe>'></iframe>";
+ setTimeout(runTest4end, 500);
+}
+
+function runTest4end() {
+ printpreview();
+ exitprintpreview();
+
+ runTest5();
+}
+
+// This is a crash test for bug 595337
+function runTest5() {
+ window.frames[0].document.body.innerHTML =
+ '<iframe style="position: fixed; visibility: hidden; bottom: 10em;"></iframe>' +
+ '<input contenteditable="true" style="display: table; page-break-before: left; width: 10000px;">';
+ printpreview();
+ exitprintpreview();
+
+ setTimeout(runTest6, 0);
+}
+
+// Crash test for bug 878037
+function runTest6() {
+ window.frames[0].document.body.innerHTML =
+ '<style> li { list-style-image: url("animated.gif"); } </style>' +
+ '<li>Firefox will crash if you try and print this page</li>';
+
+ setTimeout(runTest6end, 500);
+}
+
+function runTest6end() {
+ printpreview();
+ exitprintpreview();
+
+ finish();
+}
+
+]]></script>
+<table style="border: 1px solid black;" xmlns="http://www.w3.org/1999/xhtml">
+<tr><th>Print preview canvas 1</th><th>Print preview canvas 2</th></tr>
+<tr>
+<td><canvas height="400" width="400"></canvas></td>
+<td><canvas height="400" width="400"></canvas></td>
+</tr></table>
+</window>
diff --git a/layout/base/tests/chrome/test_bug1018265.xul b/layout/base/tests/chrome/test_bug1018265.xul
new file mode 100644
index 000000000..5787be72c
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug1018265.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1018265
+-->
+<window title="Mozilla Bug 1018265"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1018265 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ window.open("file_bug1018265.xul", "contentViewerTest", "chrome,width=100,height=100");
+ }
+
+ function done() {
+ ok(true, "done");
+ setTimeout("SimpleTest.finish()", 0);
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018265"
+ target="_blank">Mozilla Bug 1018265</a>
+ </body>
+</window>
diff --git a/layout/base/tests/chrome/test_bug1041200.xul b/layout/base/tests/chrome/test_bug1041200.xul
new file mode 100644
index 000000000..e85349697
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug1041200.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ window.open("bug1041200_window.html", "bug1041200",
+ "chrome,width=800,height=800");
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug396367-1.html b/layout/base/tests/chrome/test_bug396367-1.html
new file mode 100644
index 000000000..bf24161ca
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug396367-1.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396367
+-->
+<head>
+ <title>Test for Bug 396367</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function finish() {
+ ok(true, "didn't crash");
+ var docviewer = getdocviewer();
+ docviewer.textZoom = 1;
+ SimpleTest.finish();
+ }
+
+ function getdocviewer() {
+ var navigator1 = top.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
+ var docShell = navigator1.QueryInterface(Components.interfaces.nsIDocShell);
+ var docviewer = docShell.contentViewer;
+ return docviewer;
+ }
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396367">Mozilla Bug 396367</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+
+<input>
+<script>document.body.setAttribute('style', 'display: -moz-box; overflow: scroll;');</script>
+<script>
+var docviewer = getdocviewer();
+docviewer.textZoom=Math.floor(10*Math.random())/4+0.2;
+document.documentElement.offsetHeight;
+setTimeout(finish, 0);
+</script>
+
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug396367-2.html b/layout/base/tests/chrome/test_bug396367-2.html
new file mode 100644
index 000000000..94f218a0d
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug396367-2.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396367
+-->
+<head>
+ <title>Test for Bug 396367</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <style>select::after { content:"m"; }</style>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function finish() {
+ ok(true, "didn't crash");
+ var docviewer = getdocviewer();
+ docviewer.textZoom = 1;
+ SimpleTest.finish();
+ }
+
+ function getdocviewer() {
+ var navigator1 = top.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
+ var docShell = navigator1.QueryInterface(Components.interfaces.nsIDocShell);
+ var docviewer = docShell.contentViewer;
+ return docviewer;
+ }
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396367">Mozilla Bug 396367</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+
+<div style="overflow: scroll; float: left;">
+
+<select></select>
+
+<li style="display: table-cell;">
+
+<script>
+var docviewer = getdocviewer();
+docviewer.textZoom=Math.floor(10*Math.random())/4+0.2;
+document.documentElement.offsetHeight;
+setTimeout(finish, 0);
+</script>
+</li>
+</div>
+
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug420499.xul b/layout/base/tests/chrome/test_bug420499.xul
new file mode 100644
index 000000000..fb20c417a
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug420499.xul
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=420499
+-->
+<window title="Mozilla Bug 420499" onload="setTimeout(focusInput, 500);"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+
+
+ <menu id="menu" label="Menu">
+ <menupopup id="file-popup">
+ <!-- <textbox id="some-text" maxlength="10" value="some text"/> -->
+ <menu label="submenu">
+ <menupopup id="file-popup-inner">
+
+ <menuitem label="Item1"/>
+ <menuitem label="Item2"/>
+ <textbox id="some-text" maxlength="10" value="some more text"/>
+ </menupopup>
+ </menu>
+ <menuitem label="Item3"/>
+ <menuitem label="Item4"/>
+ </menupopup>
+ </menu>
+
+ <popupset>
+ <popup id="contextmenu">
+ <menuitem label="Cut"/>
+ <menuitem label="Copy"/>
+ <menuitem label="Paste"/>
+ </popup>
+ <tooltip id="tooltip" orient="vertical">
+ <description value="This is a tooltip"/>
+ </tooltip>
+ </popupset>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" bgcolor="white">
+
+ <p id="par1">Paragraph 1</p>
+ <p id="par2">Paragraph 2</p>
+ <p id="par3">Paragraph 3</p>
+ <p id="par4">Paragraph 4</p>
+ <p id="par5">Paragraph 5</p>
+
+ <input type="text" id="text-input" maxlength="10" value="some more text"/> <br />
+
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=420499"
+ target="_blank">Mozilla Bug 420499</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 420499 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function getSelectionController() {
+ return document.docShell
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsISelectionDisplay)
+ .QueryInterface(Components.interfaces.nsISelectionController);
+ }
+
+ function isCaretVisible() {
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var docShell = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell);
+ var selCon = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsISelectionDisplay)
+ .QueryInterface(Components.interfaces.nsISelectionController);
+ return selCon.caretVisible;
+ }
+
+ function focusInput() {
+ ok(!isCaretVisible(), "Caret shouldn't be visible");
+ $("text-input").focus();
+ ok(isCaretVisible(), "Caret should be visible when input focused");
+ window.addEventListener("popupshown", popupMenuShownHandler, false);
+ $("menu").open = true;
+ }
+
+ function popupMenuShownHandler() {
+ window.removeEventListener("popupshown", popupMenuShownHandler, false);
+ ok(!isCaretVisible(), "Caret shouldn't be visible when menu open");
+ window.addEventListener("popuphidden", ensureParagraphFocused, false);
+ $("menu").open = false;
+ }
+
+ function ensureParagraphFocused() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused, false);
+ ok(isCaretVisible(), "Caret should have returned to previous focus");
+ window.addEventListener("popupshown", popupMenuShownHandler2, false);
+ $("contextmenu").openPopup($('text-input'), "topleft" , -1 , -1 , true, true);
+ }
+
+ function popupMenuShownHandler2() {
+ window.removeEventListener("popupshown", popupMenuShownHandler2, false);
+ ok(isCaretVisible(), "Caret should be visible when context menu open");
+ window.addEventListener("popuphidden", ensureParagraphFocused2, false);
+ document.getElementById("contextmenu").hidePopup();
+ }
+
+ function ensureParagraphFocused2() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused2, false);
+ ok(isCaretVisible(), "Caret should still be visible");
+ window.addEventListener("popupshown", tooltipShownHandler, false);
+ $("tooltip").openPopup($('text-input'), "topleft" , -1 , -1 , false, true);
+ }
+
+ function tooltipShownHandler() {
+ window.removeEventListener("popupshown", tooltipShownHandler, false);
+ ok(isCaretVisible(), "Caret should be visible when tooltip is visible");
+ window.addEventListener("popuphidden", ensureParagraphFocused3, false);
+ document.getElementById("tooltip").hidePopup();
+ }
+
+ function ensureParagraphFocused3() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused2, false);
+ ok(isCaretVisible(), "Caret should still be visible");
+ SimpleTest.finish();
+ }
+ ]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug458898.html b/layout/base/tests/chrome/test_bug458898.html
new file mode 100644
index 000000000..90261b4ce
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug458898.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458898
+-->
+<head>
+ <title>Test for Bug 458898</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=458898">Mozilla Bug 458898</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var win = window.openDialog("data:text/html,<div style='height:200px; width:100px;'>");
+
+function loaded() {
+ var disableWindowResizePref = "dom.disable_window_move_resize";
+ SpecialPowers.pushPrefEnv({"set":[[disableWindowResizePref, false]]}, function() {
+ win.sizeToContent();
+ ok(win.innerWidth >= 100, "innerWidth: " + win.innerWidth + " >= 100 ?");
+ ok(win.innerHeight >= 200, "innerHeight: " + win.innerHeight + " >= 200 ?");
+ win.close();
+ SimpleTest.finish();
+ });
+}
+
+win.addEventListener("load", loaded, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug495648.xul b/layout/base/tests/chrome/test_bug495648.xul
new file mode 100644
index 000000000..186417a0d
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug495648.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495648
+-->
+<window title="Mozilla Bug 495648"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=495648"
+ target="_blank">Mozilla Bug 495648</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.expectAssertions(15, 24);
+
+ /** Test for Bug 495648 **/
+ var uri = window.location.href.replace(/test_bug495648.xul/, "bug495648.rdf");
+
+ function doTest() {
+ var list = document.getElementById('l');
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+ var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
+ var rdf = rdfService.GetDataSourceBlocking(uri);
+ list.database.AddDataSource(rdf);
+ list.builder.rebuild();
+ is(list.itemCount, 40, "Unexpected item count");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+
+ ]]>
+ </script>
+<listbox flex="1" id="l" seltype="multiple" datasources="rdf:null" ref="urn:root" sortResource="http://home.netscape.com/NC-rdf#name" sortDirection="ascending">
+<template>
+<rule><conditions><content uri="?uri" /><triple subject="?uri" predicate="http://home.netscape.com/NC-rdf#links" object="?links" /><member container="?links" child="?child" /><triple subject="?child" predicate="http://home.netscape.com/NC-rdf#name" object="?name" /></conditions><bindings><binding subject="?child" predicate="http://sitedelta.schierla.de/SD-rdf#status" object="?status" /></bindings><action><listitem label="?name" class="listitem-iconic" status="?status" uri="?child" /></action></rule>
+</template>
+</listbox>
+</window>
diff --git a/layout/base/tests/chrome/test_bug504311.xul b/layout/base/tests/chrome/test_bug504311.xul
new file mode 100644
index 000000000..1b640fdd8
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug504311.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=504311
+-->
+<window title="Mozilla Bug 504311"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=504311"
+ target="_blank">Mozilla Bug 504311</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function doTest()
+{
+ var tb = document.getElementById("tb01");
+ var inputField = tb.inputField;
+ var editor = inputField.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
+ editor = editor.QueryInterface(Components.interfaces.nsIPlaintextEditor);
+ editor.wrapWidth = -1;
+
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+}
+]]></script>
+<textbox id="tb01" multiline="true"/>
+</window>
diff --git a/layout/base/tests/chrome/test_bug514660.xul b/layout/base/tests/chrome/test_bug514660.xul
new file mode 100644
index 000000000..e7c504bcf
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug514660.xul
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514660
+-->
+<window title="Mozilla Bug 504311"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514660"
+ target="_blank">Mozilla Bug 514660</a>
+<textarea></textarea>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function doTest()
+{
+ var viewer = window
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell)
+ .contentViewer;
+ viewer.authorStyleDisabled = true;
+
+ document.documentElement.getBoundingClientRect();
+ ok(true, "Didn't crash");
+
+ viewer.authorStyleDisabled = false;
+
+ SimpleTest.finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug533845.xul b/layout/base/tests/chrome/test_bug533845.xul
new file mode 100644
index 000000000..2cee7af05
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug533845.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=533845
+-->
+<window title="Mozilla Bug 533845"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<panel id="panel" width="50" height="50" onpopupshown="continueTest()">
+ <iframe type="content" id="contentFrame" src="data:text/html,&lt;html&gt;&lt;body onclick='document.body.textContent=1'&gt;This is a panel!&lt;/body&gt;&lt;/html&gt;" width="500" height="500"/>
+</panel>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533845"
+ target="_blank">Mozilla Bug 533845</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function doTest() {
+ document.getElementById('panel').showPopup();
+}
+
+function continueTest() {
+ var ifrwindow = document.getElementById("contentFrame").contentWindow;
+ ifrwindow.focus();
+ var utils = ifrwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ var rect = ifrwindow.document.body.getBoundingClientRect();
+ var x = rect.left + (rect.width/2);
+ var y = rect.top + (rect.height/2);
+ utils.sendMouseEvent("mousedown", x, y, 0, 1, 0);
+ utils.sendMouseEvent("mouseup", x, y, 0, 1, 0);
+ is(ifrwindow.document.body.textContent, "1", "Should have got a click event!");
+ SimpleTest.finish();
+}
+
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug551434.html b/layout/base/tests/chrome/test_bug551434.html
new file mode 100644
index 000000000..10691c53a
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug551434.html
@@ -0,0 +1,97 @@
+<html>
+<head>
+ <title>Test for Bug 551434</title>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+</div>
+<pre id="test">
+<input id="i1" onkeydown="gKeyDown1++; $('i2').focus();" onkeypress="gKeyPress1++;" onkeyup="gKeyUp1++;"/>
+<input id="i2" onkeydown="gKeyDown2++;" onkeypress="gKeyPress2++;" onkeyup="gKeyUp2++;"/>
+
+<input id="i3" onkeydown="gKeyDown3++; frames[0].document.getElementById('i4').focus();"
+ onkeypress="gKeyPress3++;" onkeyup="gKeyUp3++;"/>
+<iframe id="iframe" src="http://example.org/chrome/layout/base/tests/chrome/bug551434_childframe.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var gKeyDown1 = 0, gKeyPress1 = 0, gKeyUp1 = 0;
+var gKeyDown2 = 0, gKeyPress2 = 0, gKeyUp2 = 0;
+var gKeyDown3 = 0, gKeyPress3 = 0, gKeyUp3 = 0;
+
+function runTest()
+{
+ $("i1").focus();
+
+ // key events should not be retargeted when the focus changes to an
+ // element in the same document.
+ synthesizeKey("a", { type: "keydown", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_B });
+ is(document.activeElement, $("i2"), "input 2 in focused");
+
+ synthesizeKey("a", { type: "keyup", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_B });
+
+ is(gKeyDown1, 1, "keydown on input 1");
+ is(gKeyPress1, 0, "keypress on input 1");
+ is(gKeyUp1, 0, "keyup on input 1");
+ is(gKeyDown2, 0, "keydown on input 2");
+ is(gKeyPress2, 1, "keypress on input 2");
+ is(gKeyUp2, 1, "keyup on input 2");
+
+ is($("i1").value, "", "input 1 value");
+ is($("i2").value, "a", "input 2 value");
+
+ // key events should however be retargeted when the focus changes to an
+ // element in the a content document from a chrome document.
+ $("i3").focus();
+
+ var childWinObj = frames[0].wrappedJSObject;
+
+ synthesizeKey("b", { code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+ is(gKeyDown3, 1, "keydown on input 3");
+ is(gKeyPress3, 1, "keypress on input 3");
+ is(gKeyUp3, 1, "keyup on input 3");
+ is(childWinObj.gKeyDownChild, 0, "keydown on input 4");
+ is(childWinObj.gKeyPressChild, 0, "keypress on input 4");
+ is(childWinObj.gKeyUpChild, 0, "keyup on input 4");
+
+ var i4 = frames[0].document.getElementById("i4");
+ is($("i3").value, "b", "input 3 value");
+ is(i4.value, "", "input 4 value");
+
+ is(document.activeElement, $("iframe"), "parent focus");
+ is(frames[0].document.activeElement, i4, "child focus");
+
+ // key events should also be retargeted when the focus changes to an
+ // element in a chrome document from a content document.
+ i4.addEventListener("keydown", () => $("i3").focus(), false);
+
+ synthesizeKey("c", { code: "KeyC", keyCode: KeyboardEvent.DOM_VK_C });
+
+ is(gKeyDown3, 1, "keydown on input 3");
+ is(gKeyPress3, 1, "keypress on input 3");
+ is(gKeyUp3, 1, "keyup on input 3");
+ is(childWinObj.gKeyDownChild, 1, "keydown on input 4");
+ is(childWinObj.gKeyPressChild, 1, "keypress on input 4");
+ is(childWinObj.gKeyUpChild, 1, "keyup on input 4");
+
+ is($("i3").value, "b", "input 3 value");
+ is(i4.value, "c", "input 4 value");
+
+ is(document.activeElement, $("i3"), "parent focus");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/chrome/test_bug708062.html b/layout/base/tests/chrome/test_bug708062.html
new file mode 100644
index 000000000..dcf4481ea
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug708062.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=708062
+-->
+<head>
+ <title>Test for Bug 708062</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="doTest()">
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=708062">Mozilla Bug 708062</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<iframe id="f" style="width:100px;"
+ src="data:text/html,A<div id='d' style='position:fixed;width:170px;top:0;right:0;height:1px;background:yellow;'>"></iframe>
+<pre id="test">
+
+<script>
+function isBoundingClientRect(e, r, msg) {
+ var BCR = e.getBoundingClientRect();
+ is([BCR.left, BCR.top, BCR.right, BCR.bottom].join(','), r, msg);
+}
+
+function doTest() {
+ var f = document.getElementById('f');
+
+ var navigator1 = f.contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
+ var docShell = navigator1.QueryInterface(Components.interfaces.nsIDocShell);
+ var docviewer = docShell.contentViewer;
+
+ var d = f.contentDocument.getElementById('d');
+
+ isBoundingClientRect(d, "-70,0,100,1", "initial rect");
+ docviewer.fullZoom = 2;
+ isBoundingClientRect(d, "-120,0,50,1", "after zooming in");
+ docviewer.fullZoom = 1;
+ isBoundingClientRect(d, "-70,0,100,1", "after zooming back out");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug812817.xul b/layout/base/tests/chrome/test_bug812817.xul
new file mode 100644
index 000000000..d9e1d59ed
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug812817.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=812817
+-->
+<window title="Mozilla Bug 812817"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<panel id="panel" width="200" height="200" onpopupshown="continueTest()">
+</panel>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=812817"
+ target="_blank">Mozilla Bug 812817</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var panel = document.getElementById('panel');
+function doTest() {
+ panel.openPopup(null, '', 500, 500, false, false, null);
+}
+
+function continueTest() {
+ panel.style.background = "url(blue-32x32.png)";
+ setTimeout(function() {
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ }, 50);
+}
+
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug847890_paintFlashing.html b/layout/base/tests/chrome/test_bug847890_paintFlashing.html
new file mode 100644
index 000000000..9653e48d9
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug847890_paintFlashing.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for paint flashing</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ function startTest() {
+ waitForAllPaintsFlushed(function () {
+ var before = snapshotWindow(window, false);
+ SpecialPowers.getDOMWindowUtils(window).paintFlashing = true;
+ document.body.innerHTML = "bar";
+ waitForAllPaintsFlushed(function () {
+ document.body.innerHTML = "foo";
+ waitForAllPaintsFlushed(function () {
+ var after = snapshotWindow(window, false);
+ ok(compareSnapshots(before, after, false)[0], "windows are different");
+ SpecialPowers.getDOMWindowUtils(window).paintFlashing = false;
+ SimpleTest.finish();
+ });
+ });
+ });
+ }
+ </script>
+</head>
+<body onload="startTest()">foo</body>
+</html>
diff --git a/layout/base/tests/chrome/test_chrome_content_integration.xul b/layout/base/tests/chrome/test_chrome_content_integration.xul
new file mode 100644
index 000000000..94878447a
--- /dev/null
+++ b/layout/base/tests/chrome/test_chrome_content_integration.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ var root = getRootDirectory(window.location.href);
+ window.open(root + "chrome_content_integration_window.xul", "chrome_content_integration",
+ "chrome,width=200,height=300");
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_chrome_over_plugin.xul b/layout/base/tests/chrome/test_chrome_over_plugin.xul
new file mode 100644
index 000000000..9d352bdfa
--- /dev/null
+++ b/layout/base/tests/chrome/test_chrome_over_plugin.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ var w = window.open("chrome_over_plugin_window.xul", "chrome_over_plugin",
+ "chrome,width=200,height=300");
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_default_background.xul b/layout/base/tests/chrome/test_default_background.xul
new file mode 100644
index 000000000..e278e58b6
--- /dev/null
+++ b/layout/base/tests/chrome/test_default_background.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ window.open("default_background_window.xul", "default_background",
+ "chrome,width=200,height=300");
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_dialog_with_positioning.html b/layout/base/tests/chrome/test_dialog_with_positioning.html
new file mode 100644
index 000000000..048197a42
--- /dev/null
+++ b/layout/base/tests/chrome/test_dialog_with_positioning.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test positioning of fixed-pos/abs-pos elements in a XUL dialog</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var root = getRootDirectory(window.location.href);
+window.openDialog(root + "dialog_with_positioning_window.xul", "dialog_with_positioning",
+ "dialog,chrome");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html b/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html
new file mode 100644
index 000000000..0cd9b9abb
--- /dev/null
+++ b/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that we don't get unnecessary repaints with fixed backgrounds</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)" style="background:url(blue-32x32.png) top left no-repeat fixed; background-size: 100px 2000px; overflow:hidden;">
+<div style="height: 2048px"></div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+function startTest() {
+ // Do a scroll to ensure we trigger activity heuristics.
+ document.documentElement.scrollTop = 1;
+ waitForAllPaintsFlushed(function () {
+ document.documentElement.scrollTop = 0;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state and scroll down
+ utils.checkAndClearPaintedState(document.documentElement);
+ document.documentElement.scrollTop = 100;
+ waitForAllPaintsFlushed(function () {
+ // Make sure nothing painted
+ var painted = utils.checkAndClearPaintedState(document.documentElement);
+ is(painted, false, "Fixed background should not have been painted when scrolled");
+ SimpleTest.finish();
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul b/layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul
new file mode 100644
index 000000000..c3cce54e5
--- /dev/null
+++ b/layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul
@@ -0,0 +1,114 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var winLowerThanVista = navigator.platform.indexOf("Win") == 0;
+ if (winLowerThanVista) {
+ var version = Components.classes["@mozilla.org/system-info;1"]
+ .getService(Components.interfaces.nsIPropertyBag2)
+ .getProperty("version");
+ winLowerThanVista = parseFloat(version) < 6.0;
+ }
+
+ var tests = [{maximize: false}, {maximize: true}];
+ var testIndex = 0;
+ var win;
+
+ function testInfo() {
+ return tests[testIndex].maximize ? " (maximized)" : " (non-maximized)";
+ }
+
+ function doTest(evt) {
+ var initialCount = win.mozPaintCount;
+
+ function nextStep() {
+ if (win.mozPaintCount == initialCount || win.isMozAfterPaintPending) {
+ SimpleTest.info("Waiting for mozPaintCount (= " + initialCount + ") to increase" + testInfo());
+ // Do not use SimpleTest.executeSoon() here: give a little more time.
+ setTimeout(nextStep, 100);
+ return;
+ }
+
+ isnot(win.mozPaintCount, initialCount, "mozPaintCount has increased" + testInfo());
+
+ function testLeafLayersPartitionWindow() {
+ var success = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .leafLayersPartitionWindow();
+ // "[leafLayersPartitionWindow()] Always returns true in non-DEBUG builds."
+ // To prevent random failures on Windows, try to run this again after a timeout.
+ if (!success && navigator.platform.indexOf("Win") >= 0) {
+ setTimeout(testLeafLayersPartitionWindow, 100);
+ return;
+ }
+ ok(success,
+ "Leaf layers should form a non-overlapping partition of the browser window" + testInfo() +
+ (success ? "" : ". [Set MOZ_DUMP_PAINT_LIST=1 env var to investigate.]"));
+
+ win.close();
+ ++testIndex;
+ nextTest();
+ }
+ testLeafLayersPartitionWindow();
+ }
+
+ if (tests[testIndex].maximize) {
+ function resizeListener() {
+ win.removeEventListener("resize", resizeListener, true);
+ // We want a paint after resize.
+ initialCount = win.mozPaintCount;
+ SimpleTest.executeSoon(nextStep);
+ }
+ win.addEventListener("resize", resizeListener, true);
+ SimpleTest.info("Maximizing test " + testIndex + " window" + testInfo());
+ Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser")
+ .maximize();
+ } else {
+ SimpleTest.executeSoon(nextStep);
+ }
+ }
+
+ function nextTest() {
+ if (testIndex >= tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (winLowerThanVista && !tests[testIndex].maximize) {
+ ok(true, "Skipping non-maximized test " + testIndex + " on winLowerThanVista since the resizer causes overlapping layers");
+ ++testIndex;
+ nextTest();
+ return;
+ }
+
+ window.focus();
+ // Run the test in a separate window so we get a clean browser window.
+ win = window.open("data:text/html,<html style='overflow:scroll'>",
+ "", "scrollbars=yes,toolbar,menubar,width=500,height=500");
+ setTimeout(setupWindow, 0);
+ }
+
+ function setupWindow() {
+ win.addEventListener("load", doTest, false);
+ win.focus();
+ }
+
+ nextTest();
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_no_clip_iframe.xul b/layout/base/tests/chrome/test_no_clip_iframe.xul
new file mode 100644
index 000000000..b8e74d8ea
--- /dev/null
+++ b/layout/base/tests/chrome/test_no_clip_iframe.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ window.open("no_clip_iframe_window.xul", "no_clip_iframe",
+ "chrome,width=200,height=400");
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_prerendered_transforms.html b/layout/base/tests/chrome/test_prerendered_transforms.html
new file mode 100644
index 000000000..86893303a
--- /dev/null
+++ b/layout/base/tests/chrome/test_prerendered_transforms.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that active transformed elements coming into view are prerendered so we don't have to redraw constantly</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest()">
+<div>
+<div id="t" style="position:absolute; left:0; top:500px; -moz-transform: translatex(-100px); width:200px; height:100px; background:yellow;">
+ <div style="text-align:right">Hello</div>
+ <div style="text-align:left">Kitty</div>
+</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+function startTest() {
+ // Do a couple of transform changes to ensure we've triggered activity heuristics
+ waitForAllPaintsFlushed(function () {
+ t.style.MozTransform = "translatex(-75px)";
+ waitForAllPaintsFlushed(function () {
+ t.style.MozTransform = "translatex(-50px)";
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and move again.
+ utils.checkAndClearPaintedState(t);
+ // Don't move to 0 since that might trigger some special case that turns off transforms.
+ t.style.MozTransform = "translatex(-1px)";
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(t);
+ is(painted, false, "Transformed element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_printpreview.xul b/layout/base/tests/chrome/test_printpreview.xul
new file mode 100644
index 000000000..6a63791f2
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview.xul
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+window.open("printpreview_helper.xul", "printpreview", "chrome,width=100,height=100");
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_printpreview_bug396024.xul b/layout/base/tests/chrome/test_printpreview_bug396024.xul
new file mode 100644
index 000000000..eb086d35a
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview_bug396024.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396024
+-->
+<window title="Mozilla Bug 369024"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=396024"
+ target="_blank">Mozilla Bug 396024</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+window.open("printpreview_bug396024_helper.xul", "bug396024", "chrome,width=100,height=100");
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_printpreview_bug482976.xul b/layout/base/tests/chrome/test_printpreview_bug482976.xul
new file mode 100644
index 000000000..52918d5ba
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview_bug482976.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482976
+-->
+<window title="Mozilla Bug 482976"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=482976"
+ target="_blank">Mozilla Bug 482976</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+window.open("printpreview_bug482976_helper.xul", "bug482976", "chrome,width=100,height=100");
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_scrolling_repaints.html b/layout/base/tests/chrome/test_scrolling_repaints.html
new file mode 100644
index 000000000..ff62aa5db
--- /dev/null
+++ b/layout/base/tests/chrome/test_scrolling_repaints.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that we don't get unnecessary repaints due to subpixel shifts</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)">
+<div id="t" style="width:400px; height:100px; background:yellow; overflow:hidden">
+ <div style="height:40px;"></div>
+ <div id="e" style="height:30px; background:lime"></div>
+ <div style="height:60.4px; background:pink"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+function startTest() {
+ // Do a scroll to ensure we trigger activity heuristics.
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ // Scroll down as far as we can, to put our rendering layer at a subpixel offset within the layer
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 1000;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ // scroll up a little bit. This should not cause anything to be repainted.
+ t.scrollTop = t.scrollTop - 10;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_will_change.html b/layout/base/tests/chrome/test_will_change.html
new file mode 100644
index 000000000..fb8122b0b
--- /dev/null
+++ b/layout/base/tests/chrome/test_will_change.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for MozAfterPaint</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ #checkOpacityRepaint {
+ will-change: opacity;
+ }
+ #checkTransformRepaint {
+ will-change: transform;
+ }
+ div {
+ width: 100px;
+ height: 100px;
+ background: radial-gradient(ellipse at center, #87e0fd 0%,#53cbf1 40%,#05abe0 100%);
+ }
+ </style>
+</head>
+<body>
+ <div id="checkRepaint">
+ Check repaint without will-change
+ </div>
+ <div id="checkOpacityRepaint">
+ Check repaint with will-change
+ </div>
+ <div id="checkTransformRepaint">
+ Check repaint with will-change
+ </div>
+</body>
+<script>
+
+var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+var initialPaintCount = 0;
+
+function test_checkRepaint(next) {
+ var element = document.getElementById("checkRepaint");
+ waitForAllPaintsFlushed(function () {
+ utils.checkAndClearPaintedState(element);
+ element.style.opacity = "0.5";
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(element);
+ // *** We check that this repaints because the test is relying
+ // on this property. If this is broken then this test wont
+ // be reliable check for will-change.
+ is(painted, true, "element should have been painted");
+ next();
+ });
+ });
+}
+
+function test_checkOpacityRepaint(next) {
+ var element = document.getElementById("checkOpacityRepaint");
+ waitForAllPaintsFlushed(function () {
+ utils.checkAndClearPaintedState(element);
+ element.style.opacity = "0.5";
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:opacity,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change checkOpacityRepaint element should not have been painted");
+ }
+ next();
+ });
+ });
+}
+
+function test_checkTransformRepaint(next) {
+ var element = document.getElementById("checkTransformRepaint");
+ waitForAllPaintsFlushed(function () {
+ utils.checkAndClearPaintedState(element);
+ element.style.transform = "translateY(-5px)";
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:transform,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change checkTransformRepaint element should not have been painted");
+ }
+ next();
+ });
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+test_checkRepaint(function(){
+ test_checkOpacityRepaint(function(){
+ test_checkTransformRepaint(function(){
+ SimpleTest.finish();
+ });
+ });
+});
+
+</script>
+</html>
diff --git a/layout/base/tests/file_bug607529.html b/layout/base/tests/file_bug607529.html
new file mode 100644
index 000000000..9fbd7f393
--- /dev/null
+++ b/layout/base/tests/file_bug607529.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script>
+ window.onerror = function(msg, url, line) {
+ var myMsg = JSON.stringify({msg: msg, url: url, line: line, error: true});
+ opener.postMessage(myMsg, "*");
+ }
+
+ var report = false;
+
+ function g() {
+ if (report) {
+ opener.postMessage("callbackHappened", "*");
+ }
+ window.requestAnimationFrame(g);
+ }
+ g();
+
+ window.onload = function() {
+ opener.postMessage("loaded", "*");
+ }
+
+ addEventListener("pagehide", function f(e) {
+ if (!e.persisted && !report) {
+ opener.postMessage("notcached", "*");
+ }
+ }, false);
+
+ addEventListener("pageshow", function f(e) {
+ if (e.persisted) {
+ opener.postMessage("revived", "*");
+ }
+ }, false);
+
+ window.onmessage = function (e) {
+ if (e.data == "report") {
+ report = true;
+ }
+ };
+
+</script>
diff --git a/layout/base/tests/file_bug842853.html b/layout/base/tests/file_bug842853.html
new file mode 100644
index 000000000..12bb56506
--- /dev/null
+++ b/layout/base/tests/file_bug842853.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug </title>
+<link rel="stylesheet" href="file_bug842853.sjs">
+</head>
+<body>
+
+<a href="#anchor">Click to scroll to anchor</a><div style="height:5000px"></div><a name="anchor">FAIL</a>
+<script>window.parent.runTest()</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/file_bug842853.sjs b/layout/base/tests/file_bug842853.sjs
new file mode 100644
index 000000000..1586aaf48
--- /dev/null
+++ b/layout/base/tests/file_bug842853.sjs
@@ -0,0 +1,14 @@
+var timer;
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("body { background:lime; color:red; }");
+ response.processAsync();
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function() {
+ response.finish();
+ }, 500, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/layout/base/tests/image_rgrg-256x256.png b/layout/base/tests/image_rgrg-256x256.png
new file mode 100644
index 000000000..e6fba3daa
--- /dev/null
+++ b/layout/base/tests/image_rgrg-256x256.png
Binary files differ
diff --git a/layout/base/tests/image_rrgg-256x256.png b/layout/base/tests/image_rrgg-256x256.png
new file mode 100644
index 000000000..7f6351565
--- /dev/null
+++ b/layout/base/tests/image_rrgg-256x256.png
Binary files differ
diff --git a/layout/base/tests/input-invalid-ref.html b/layout/base/tests/input-invalid-ref.html
new file mode 100644
index 000000000..4b34c9a2f
--- /dev/null
+++ b/layout/base/tests/input-invalid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:red">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-maxlength-invalid-change.html b/layout/base/tests/input-maxlength-invalid-change.html
new file mode 100644
index 000000000..4056682a4
--- /dev/null
+++ b/layout/base/tests/input-maxlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="2" value="fooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-ui-invalid-change.html b/layout/base/tests/input-maxlength-ui-invalid-change.html
new file mode 100644
index 000000000..609838f16
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is -moz-ui-invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="2" value="fooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-ui-valid-change.html b/layout/base/tests/input-maxlength-ui-valid-change.html
new file mode 100644
index 000000000..4cd332182
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is -moz-ui-valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {}); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="3" value="foooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-valid-before-change.html b/layout/base/tests/input-maxlength-valid-before-change.html
new file mode 100644
index 000000000..8662e8f5f
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is valid until the user edits it, even if it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <input id="input" maxlength="2" value="foo">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-maxlength-valid-change.html b/layout/base/tests/input-maxlength-valid-change.html
new file mode 100644
index 000000000..bf02a8040
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {}); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="3" value="foooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-invalid-change.html b/layout/base/tests/input-minlength-invalid-change.html
new file mode 100644
index 000000000..cc2107e3d
--- /dev/null
+++ b/layout/base/tests/input-minlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('o', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="4" value="fo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-ui-invalid-change.html b/layout/base/tests/input-minlength-ui-invalid-change.html
new file mode 100644
index 000000000..cf00703ad
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is -moz-ui-invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('o', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="4" value="fo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-ui-valid-change.html b/layout/base/tests/input-minlength-ui-valid-change.html
new file mode 100644
index 000000000..830c4acfa
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is -moz-ui-valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('o', {}); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey('o', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="3" value="f">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-valid-before-change.html b/layout/base/tests/input-minlength-valid-before-change.html
new file mode 100644
index 000000000..21e692792
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is valid until the user edits it, even if it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <input id="input" minlength="5" value="foo">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-minlength-valid-change.html b/layout/base/tests/input-minlength-valid-change.html
new file mode 100644
index 000000000..40a282682
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey('o', {}); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey('o', {});
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="3" value="f">
+ </body>
+</html>
diff --git a/layout/base/tests/input-ui-valid-ref.html b/layout/base/tests/input-ui-valid-ref.html
new file mode 100644
index 000000000..76d938667
--- /dev/null
+++ b/layout/base/tests/input-ui-valid-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:green">
+ </body>
+</html>
diff --git a/layout/base/tests/input-valid-ref.html b/layout/base/tests/input-valid-ref.html
new file mode 100644
index 000000000..ec01bb98f
--- /dev/null
+++ b/layout/base/tests/input-valid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:green">
+ </body>
+</html>
+
diff --git a/layout/base/tests/marionette/manifest.ini b/layout/base/tests/marionette/manifest.ini
new file mode 100644
index 000000000..98428ccf5
--- /dev/null
+++ b/layout/base/tests/marionette/manifest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+run-if = buildapp == 'browser'
+
+[test_accessiblecaret_cursor_mode.py]
+[test_accessiblecaret_selection_mode.py]
diff --git a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
new file mode 100644
index 000000000..e330e4d70
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import string
+
+from marionette_driver.by import By
+from marionette_driver.marionette import Actions
+from marionette_driver.selection import SelectionManager
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ parameterized,
+)
+
+
+class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
+ '''Test cases for AccessibleCaret under cursor mode.
+
+ We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
+ caret for short.
+
+ '''
+ # Element IDs.
+ _input_id = 'input'
+ _input_padding_id = 'input-padding'
+ _textarea_id = 'textarea'
+ _textarea_one_line_id = 'textarea-one-line'
+ _contenteditable_id = 'contenteditable'
+
+ # Test html files.
+ _cursor_html = 'test_carets_cursor.html'
+
+ def setUp(self):
+ # Code to execute before every test is running.
+ super(AccessibleCaretCursorModeTestCase, self).setUp()
+ self.caret_tested_pref = 'layout.accessiblecaret.enabled'
+ self.caret_timeout_ms_pref = 'layout.accessiblecaret.timeout_ms'
+ self.hide_carets_for_mouse = 'layout.accessiblecaret.hide_carets_for_mouse_input'
+ self.prefs = {
+ self.caret_tested_pref: True,
+ self.caret_timeout_ms_pref: 0,
+ self.hide_carets_for_mouse: False,
+ }
+ self.marionette.set_prefs(self.prefs)
+ self.actions = Actions(self.marionette)
+
+ def open_test_html(self, test_html):
+ self.marionette.navigate(self.marionette.absolute_url(test_html))
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_the_right_by_one_character(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ target_content = sel.content
+ target_content = target_content[:1] + content_to_add + target_content[1:]
+
+ # Get first caret (x, y) at position 1 and 2.
+ el.tap()
+ sel.move_cursor_to_front()
+ cursor0_x, cursor0_y = sel.cursor_location()
+ first_caret0_x, first_caret0_y = sel.first_caret_location()
+ sel.move_cursor_by_offset(1)
+ first_caret1_x, first_caret1_y = sel.first_caret_location()
+
+ # Tap the front of the input to make first caret appear.
+ el.tap(cursor0_x, cursor0_y)
+
+ # Move first caret.
+ self.actions.flick(el, first_caret0_x, first_caret0_y,
+ first_caret1_x, first_caret1_y).perform()
+
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ target_content = sel.content + content_to_add
+
+ # Tap the front of the input to make first caret appear.
+ el.tap()
+ sel.move_cursor_to_front()
+ el.tap(*sel.cursor_location())
+
+ # Move first caret to the bottom-right corner of the element.
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = el.size['width'], el.size['height']
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ target_content = content_to_add + sel.content
+
+ # Get first caret location at the front.
+ el.tap()
+ sel.move_cursor_to_front()
+ dest_x, dest_y = sel.first_caret_location()
+
+ # Tap to make first caret appear. Note: it's strange that when the caret
+ # is at the end, the rect of the caret in <textarea> cannot be obtained.
+ # A bug perhaps.
+ el.tap()
+ sel.move_cursor_to_end()
+ sel.move_cursor_by_offset(1, backward=True)
+ el.tap(*sel.cursor_location())
+ src_x, src_y = sel.first_caret_location()
+
+ # Move first caret to the front of the input box.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_dragging_caret_to_top_left_corner_after_timeout(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ non_target_content = content_to_add + sel.content
+
+ # Set caret timeout to be 1 second.
+ timeout = 1
+ self.marionette.set_pref(self.caret_timeout_ms_pref, timeout * 1000)
+
+ # Set a 3x timeout margin to prevent intermittent test failures.
+ timeout *= 3
+
+ # Tap to make first caret appear. Note: it's strange that when the caret
+ # is at the end, the rect of the caret in <textarea> cannot be obtained.
+ # A bug perhaps.
+ el.tap()
+ sel.move_cursor_to_end()
+ sel.move_cursor_by_offset(1, backward=True)
+ el.tap(*sel.cursor_location())
+
+ # Wait until first caret disappears, then pretend to move it to the
+ # top-left corner of the input box.
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = 0, 0
+ self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertNotEqual(non_target_content, sel.content)
+
+ def test_caret_not_appear_when_typing_in_scrollable_content(self):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, self._input_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ non_target_content = content_to_add + sel.content + string.ascii_letters
+
+ el.tap()
+ sel.move_cursor_to_end()
+
+ # Insert a long string to the end of the <input>, which triggers
+ # ScrollPositionChanged event.
+ el.send_keys(string.ascii_letters)
+
+ # The caret should not be visible. If it does appear wrongly due to the
+ # ScrollPositionChanged event, we can drag it to the front of the
+ # <input> to change the cursor position.
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = 0, 0
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ # The content should not be inserted at the front of the <input>.
+ el.send_keys(content_to_add)
+
+ self.assertNotEqual(non_target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_caret_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ non_target_content = sel.content + content_to_add
+
+ # Goal: the cursor position is not changed after dragging the caret down
+ # on the Y-axis.
+ el.tap()
+ sel.move_cursor_to_front()
+ el.tap(*sel.cursor_location())
+ x, y = sel.first_caret_location()
+
+ # Drag the caret down by 50px, and insert '!'.
+ self.actions.flick(el, x, y, x, y + 50).perform()
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertNotEqual(non_target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_caret_not_jump_to_front_when_dragging_up_to_editable_content_boundary(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ non_target_content = content_to_add + sel.content
+
+ # Goal: the cursor position is not changed after dragging the caret down
+ # on the Y-axis.
+ el.tap()
+ sel.move_cursor_to_end()
+ sel.move_cursor_by_offset(1, backward=True)
+ el.tap(*sel.cursor_location())
+ x, y = sel.first_caret_location()
+
+ # Drag the caret up by 50px, and insert '!'.
+ self.actions.flick(el, x, y, x, y - 50).perform()
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertNotEqual(non_target_content, sel.content)
+
+ def test_drag_caret_from_front_to_end_across_columns(self):
+ self.open_test_html('test_carets_columns.html')
+ el = self.marionette.find_element(By.ID, 'columns-inner')
+ sel = SelectionManager(el)
+ content_to_add = '!'
+ target_content = sel.content + content_to_add
+
+ # Goal: the cursor position can be changed by dragging the caret from
+ # the front to the end of the content.
+
+ # Tap to make the cursor appear.
+ before_image_1 = self.marionette.find_element(By.ID, 'before-image-1')
+ before_image_1.tap()
+
+ # Tap the front of the content to make first caret appear.
+ sel.move_cursor_to_front()
+ el.tap(*sel.cursor_location())
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = el.size['width'], el.size['height']
+
+ # Drag the first caret to the bottom-right corner of the element.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, self._contenteditable_id)
+ sel = SelectionManager(el)
+ content_to_add_1 = '!'
+ content_to_add_2 = '\n\n'
+ target_content = content_to_add_1 + content_to_add_2 + sel.content
+
+ # Goal: the cursor position can be changed by dragging the caret from
+ # the end of the content to the front br element. Because we cannot get
+ # caret location if it's on a br element, we need to get the first caret
+ # location then adding the new lines.
+
+ # Get first caret location at the front.
+ el.tap()
+ sel.move_cursor_to_front()
+ dest_x, dest_y = sel.first_caret_location()
+
+ # Append new line to the front of the content.
+ el.send_keys(content_to_add_2);
+
+ # Tap to make first caret appear.
+ el.tap()
+ sel.move_cursor_to_end()
+ sel.move_cursor_by_offset(1, backward=True)
+ el.tap(*sel.cursor_location())
+ src_x, src_y = sel.first_caret_location()
+
+ # Move first caret to the front of the input box.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.key_down(content_to_add_1).key_up(content_to_add_1).perform()
+ self.assertEqual(target_content, sel.content)
diff --git a/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
new file mode 100644
index 000000000..2cc93dd74
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
@@ -0,0 +1,632 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+
+from marionette_driver.by import By
+from marionette_driver.marionette import Actions
+from marionette_driver.selection import SelectionManager
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ SkipTest,
+ parameterized
+)
+
+
+def skip_if_not_rotatable(target):
+ def wrapper(self, *args, **kwargs):
+ if not self.marionette.session_capabilities.get('rotatable'):
+ raise SkipTest('skipping due to device not rotatable')
+ return target(self, *args, **kwargs)
+ return wrapper
+
+
+class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
+ '''Test cases for AccessibleCaret under selection mode.'''
+ # Element IDs.
+ _input_id = 'input'
+ _input_padding_id = 'input-padding'
+ _textarea_id = 'textarea'
+ _textarea2_id = 'textarea2'
+ _textarea_one_line_id = 'textarea-one-line'
+ _textarea_rtl_id = 'textarea-rtl'
+ _contenteditable_id = 'contenteditable'
+ _contenteditable2_id = 'contenteditable2'
+ _content_id = 'content'
+ _content2_id = 'content2'
+ _non_selectable_id = 'non-selectable'
+
+ # Test html files.
+ _selection_html = 'test_carets_selection.html'
+ _multipleline_html = 'test_carets_multipleline.html'
+ _multiplerange_html = 'test_carets_multiplerange.html'
+ _longtext_html = 'test_carets_longtext.html'
+ _iframe_html = 'test_carets_iframe.html'
+ _display_none_html = 'test_carets_display_none.html'
+
+ def setUp(self):
+ # Code to execute before every test is running.
+ super(AccessibleCaretSelectionModeTestCase, self).setUp()
+ self.carets_tested_pref = 'layout.accessiblecaret.enabled'
+ self.prefs = {
+ 'layout.word_select.eat_space_to_next_word': False,
+ self.carets_tested_pref: True,
+ }
+ self.marionette.set_prefs(self.prefs)
+ self.actions = Actions(self.marionette)
+
+ def open_test_html(self, test_html):
+ self.marionette.navigate(self.marionette.absolute_url(test_html))
+
+ def word_offset(self, text, ordinal):
+ 'Get the character offset of the ordinal-th word in text.'
+ tokens = re.split(r'(\S+)', text) # both words and spaces
+ spaces = tokens[0::2] # collect spaces at odd indices
+ words = tokens[1::2] # collect word at even indices
+
+ if ordinal >= len(words):
+ raise IndexError('Only %d words in text, but got ordinal %d' %
+ (len(words), ordinal))
+
+ # Cursor position of the targeting word is behind the the first
+ # character in the word. For example, offset to 'def' in 'abc def' is
+ # between 'd' and 'e'.
+ offset = len(spaces[0]) + 1
+ offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
+ return offset
+
+ def test_word_offset(self):
+ text = ' ' * 3 + 'abc' + ' ' * 3 + 'def'
+
+ self.assertTrue(self.word_offset(text, 0), 4)
+ self.assertTrue(self.word_offset(text, 1), 10)
+ with self.assertRaises(IndexError):
+ self.word_offset(text, 2)
+
+ def word_location(self, el, ordinal):
+ '''Get the location (x, y) of the ordinal-th word in el.
+
+ The ordinal starts from 0.
+
+ Note: this function has a side effect which changes focus to the
+ target element el.
+
+ '''
+ sel = SelectionManager(el)
+ offset = self.word_offset(sel.content, ordinal)
+
+ # Move the blinking cursor to the word.
+ el.tap()
+ sel.move_cursor_to_front()
+ sel.move_cursor_by_offset(offset)
+ x, y = sel.cursor_location()
+
+ return x, y
+
+ def rect_relative_to_window(self, el):
+ '''Get element's bounding rectangle.
+
+ This function is similar to el.rect, but the coordinate is relative to
+ the top left corner of the window instead of the document.
+
+ '''
+ return self.marionette.execute_script('''
+ let rect = arguments[0].getBoundingClientRect();
+ return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
+ ''', script_args=[el])
+
+ def long_press_on_location(self, el, x=None, y=None):
+ '''Long press the location (x, y) to select a word.
+
+ If no (x, y) are given, it will be targeted at the center of the
+ element. On Windows, those spaces after the word will also be selected.
+ This function sends synthesized eMouseLongTap to gecko.
+
+ '''
+ rect = self.rect_relative_to_window(el)
+ target_x = rect['x'] + (x if x is not None else rect['width'] // 2)
+ target_y = rect['y'] + (y if y is not None else rect['height'] // 2)
+
+ self.marionette.execute_script('''
+ let Ci = Components.interfaces;
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.sendTouchEventToWindow('touchstart', [0],
+ [arguments[0]], [arguments[1]],
+ [1], [1], [0], [1], 1, 0);
+ utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
+ 0, 1, 0);
+ utils.sendTouchEventToWindow('touchend', [0],
+ [arguments[0]], [arguments[1]],
+ [1], [1], [0], [1], 1, 0);
+ ''', script_args=[target_x, target_y], sandbox='system')
+
+ def long_press_on_word(self, el, wordOrdinal):
+ x, y = self.word_location(el, wordOrdinal)
+ self.long_press_on_location(el, x, y)
+
+ def to_unix_line_ending(self, s):
+ """Changes all Windows/Mac line endings in s to UNIX line endings."""
+
+ return s.replace('\r\n', '\n').replace('\r', '\n')
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_long_press_to_select_a_word(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_long_press_to_select_a_word(el)
+
+ def _test_long_press_to_select_a_word(self, el):
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 2, 'Expect at least two words in the content.')
+ target_content = words[0]
+
+ # Goal: Select the first word.
+ self.long_press_on_word(el, 0)
+
+ # Ignore extra spaces selected after the word.
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_drag_carets(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')
+
+ # Goal: Select all text after the first word.
+ target_content = original_content[len(words[0]):]
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ el.tap()
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(el, 0)
+
+ # Drag the second caret to the end of the content.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()
+
+ # Drag the first caret to the previous position of the second caret.
+ self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_drag_swappable_carets(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')
+
+ target_content1 = words[0]
+ target_content2 = original_content[len(words[0]):]
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ el.tap()
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(el, 0)
+
+ # Drag the first caret to the end and back to where it was
+ # immediately. The selection range should not be collapsed.
+ caret1_x, caret1_y = sel.first_caret_location()
+ self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y)\
+ .flick(el, end_caret_x, end_caret_y, caret1_x, caret1_y).perform()
+ self.assertEqual(target_content1, sel.selected_content)
+
+ # Drag the first caret to the end.
+ caret1_x, caret1_y = sel.first_caret_location()
+ self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform()
+ self.assertEqual(target_content2, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_minimum_select_one_character(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_minimum_select_one_character(el)
+
+ @parameterized(_textarea2_id, el_id=_textarea2_id)
+ @parameterized(_contenteditable2_id, el_id=_contenteditable2_id)
+ @parameterized(_content2_id, el_id=_content2_id)
+ def test_minimum_select_one_character2(self, el_id):
+ self.open_test_html(self._multipleline_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_minimum_select_one_character(el)
+
+ def _test_minimum_select_one_character(self, el, x=None, y=None):
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+ el.tap()
+
+ # Goal: Select the first character.
+ target_content = original_content[0]
+
+ if x and y:
+ # If we got x and y from the arguments, use it as a hint of the
+ # location of the first word
+ pass
+ else:
+ x, y = self.word_location(el, 0)
+ self.long_press_on_location(el, x, y)
+
+ # Drag the second caret to the end of the content.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()
+
+ # Drag the second caret to the position of the first caret.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id + '_to_' + _textarea_id,
+ el1_id=_input_id, el2_id=_textarea_id)
+ @parameterized(_input_id + '_to_' + _contenteditable_id,
+ el1_id=_input_id, el2_id=_contenteditable_id)
+ @parameterized(_input_id + '_to_' + _content_id,
+ el1_id=_input_id, el2_id=_content_id)
+ @parameterized(_textarea_id + '_to_' + _input_id,
+ el1_id=_textarea_id, el2_id=_input_id)
+ @parameterized(_textarea_id + '_to_' + _contenteditable_id,
+ el1_id=_textarea_id, el2_id=_contenteditable_id)
+ @parameterized(_textarea_id + '_to_' + _content_id,
+ el1_id=_textarea_id, el2_id=_content_id)
+ @parameterized(_contenteditable_id + '_to_' + _input_id,
+ el1_id=_contenteditable_id, el2_id=_input_id)
+ @parameterized(_contenteditable_id + '_to_' + _textarea_id,
+ el1_id=_contenteditable_id, el2_id=_textarea_id)
+ @parameterized(_contenteditable_id + '_to_' + _content_id,
+ el1_id=_contenteditable_id, el2_id=_content_id)
+ @parameterized(_content_id + '_to_' + _input_id,
+ el1_id=_content_id, el2_id=_input_id)
+ @parameterized(_content_id + '_to_' + _textarea_id,
+ el1_id=_content_id, el2_id=_textarea_id)
+ @parameterized(_content_id + '_to_' + _contenteditable_id,
+ el1_id=_content_id, el2_id=_contenteditable_id)
+ def test_long_press_changes_focus_from(self, el1_id, el2_id):
+ '''Test the focus could be changed from el1 to el2 by long press.
+
+ If the focus is changed to e2 successfully, the carets should appear and
+ could be dragged.
+
+ '''
+ # Goal: Tap to focus el1, and then select the first character on
+ # el2.
+
+ # We want to collect the location of the first word in el2 here
+ # since self.word_location() has the side effect which would
+ # change the focus.
+ self.open_test_html(self._selection_html)
+ el1 = self.marionette.find_element(By.ID, el1_id)
+ el2 = self.marionette.find_element(By.ID, el2_id)
+ x, y = self.word_location(el2, 0)
+ el1.tap()
+ self._test_minimum_select_one_character(el2, x=x, y=y)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_focus_not_changed_by_long_press_on_non_selectable(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ non_selectable = self.marionette.find_element(By.ID, self._non_selectable_id)
+
+ # Goal: Focus remains on the editable element el after long pressing on
+ # the non-selectable element.
+ sel = SelectionManager(el)
+ self.long_press_on_word(el, 0)
+ self.long_press_on_location(non_selectable)
+ active_sel = SelectionManager(self.marionette.get_active_element())
+ self.assertEqual(sel.content, active_sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_handle_tilt_when_carets_overlap_each_other(self, el_id):
+ '''Test tilt handling when carets overlap to each other.
+
+ Let the two carets overlap each other. If they are set to tilted
+ successfully, tapping the tilted carets should not cause the selection
+ to be collapsed and the carets should be draggable.
+
+ '''
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')
+
+ # Goal: Select the first word.
+ self.long_press_on_word(el, 0)
+ target_content = sel.selected_content
+
+ # Drag the first caret to the position of the second caret to trigger
+ # carets overlapping.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
+
+ # We make two hit tests targeting the left edge of the left tilted caret
+ # and the right edge of the right tilted caret. If either of the hits is
+ # missed, selection would be collapsed and both carets should not be
+ # draggable.
+ (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.carets_location()
+
+ # The following values are from ua.css and all.js
+ caret_width = float(self.marionette.get_pref('layout.accessiblecaret.width'))
+ caret_margin_left = float(self.marionette.get_pref('layout.accessiblecaret.margin-left'))
+ tilt_right_margin_left = 0.41 * caret_width
+ tilt_left_margin_left = -0.39 * caret_width
+
+ left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
+ el.tap(left_caret_left_edge_x + 2, caret3_y)
+
+ right_caret_right_edge_x = (caret4_x + caret_margin_left +
+ tilt_right_margin_left + caret_width)
+ el.tap(right_caret_right_edge_x - 2, caret4_y)
+
+ # Drag the first caret back to the initial selection, the first word.
+ self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ def test_drag_caret_over_non_selectable_field(self):
+ '''Test dragging the caret over a non-selectable field.
+
+ The selected content should exclude non-selectable elements and the
+ second caret should appear in last range's position.
+
+ '''
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, 'bd')
+ sel3 = self.marionette.find_element(By.ID, 'sel3')
+ sel4 = self.marionette.find_element(By.ID, 'sel4')
+ sel6 = self.marionette.find_element(By.ID, 'sel6')
+
+ # Select target element and get target caret location
+ self.long_press_on_word(sel4, 3)
+ sel = SelectionManager(body)
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(sel6, 0)
+ end_caret2_x, end_caret2_y = sel.second_caret_location()
+
+ # Select start element
+ self.long_press_on_word(sel3, 3)
+
+ # Drag end caret to target location
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
+ 'this 3\nuser can select this')
+
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
+ 'this 3\nuser can select this 4\nuser can select this 5\nuser')
+
+ # Drag first caret to target location
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
+ '4\nuser can select this 5\nuser')
+
+ def test_drag_swappable_caret_over_non_selectable_field(self):
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, 'bd')
+ sel3 = self.marionette.find_element(By.ID, 'sel3')
+ sel4 = self.marionette.find_element(By.ID, 'sel4')
+ sel = SelectionManager(body)
+
+ self.long_press_on_word(sel4, 3)
+ (end_caret1_x, end_caret1_y), (end_caret2_x, end_caret2_y) = sel.carets_location()
+
+ self.long_press_on_word(sel3, 3)
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+
+ # Drag the first caret down, which will across the second caret.
+ self.actions.flick(body, caret1_x, caret1_y, end_caret1_x, end_caret1_y).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
+ '3\nuser can select')
+
+ # The old second caret becomes the first caret. Drag it down again.
+ self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
+ 'this')
+
+ def test_drag_caret_to_beginning_of_a_line(self):
+ '''Bug 1094056
+ Test caret visibility when caret is dragged to beginning of a line
+ '''
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, 'bd')
+ sel1 = self.marionette.find_element(By.ID, 'sel1')
+ sel2 = self.marionette.find_element(By.ID, 'sel2')
+
+ # Select the first word in the second line
+ self.long_press_on_word(sel2, 0)
+ sel = SelectionManager(body)
+ (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.carets_location()
+
+ # Select target word in the first line
+ self.long_press_on_word(sel1, 2)
+
+ # Drag end caret to the beginning of the second line
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()
+
+ # Drag end caret back to the target word
+ self.actions.flick(body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()
+
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'select')
+
+ @skip_if_not_rotatable
+ def test_caret_position_after_changing_orientation_of_device(self):
+ '''Bug 1094072
+ If positions of carets are updated correctly, they should be draggable.
+ '''
+ self.open_test_html(self._longtext_html)
+ body = self.marionette.find_element(By.ID, 'bd')
+ longtext = self.marionette.find_element(By.ID, 'longtext')
+
+ # Select word in portrait mode, then change to landscape mode
+ self.marionette.set_orientation('portrait')
+ self.long_press_on_word(longtext, 12)
+ sel = SelectionManager(body)
+ (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.carets_location()
+ self.marionette.set_orientation('landscape')
+ (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.carets_location()
+
+ # Drag end caret to the start caret to change the selected content
+ self.actions.flick(body, l_end_caret_x, l_end_caret_y,
+ l_start_caret_x, l_start_caret_y).perform()
+
+ # Change orientation back to portrait mode to prevent affecting
+ # other tests
+ self.marionette.set_orientation('portrait')
+
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'o')
+
+ def test_select_word_inside_an_iframe(self):
+ '''Bug 1088552
+ The scroll offset in iframe should be taken into consideration properly.
+ In this test, we scroll content in the iframe to the bottom to cause a
+ huge offset. If we use the right coordinate system, selection should
+ work. Otherwise, it would be hard to trigger select word.
+ '''
+ self.open_test_html(self._iframe_html)
+ iframe = self.marionette.find_element(By.ID, 'frame')
+
+ # switch to inner iframe and scroll to the bottom
+ self.marionette.switch_to_frame(iframe)
+ self.marionette.execute_script(
+ 'document.getElementById("bd").scrollTop += 999')
+
+ # long press to select bottom text
+ body = self.marionette.find_element(By.ID, 'bd')
+ sel = SelectionManager(body)
+ self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
+ self.long_press_on_location(self._bottomtext)
+
+ self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), '')
+
+ def test_carets_initialized_in_display_none(self):
+ '''Test AccessibleCaretEventHub is properly initialized on a <html> with
+ display: none.
+
+ '''
+ self.open_test_html(self._display_none_html)
+ html = self.marionette.find_element(By.ID, 'html')
+ content = self.marionette.find_element(By.ID, 'content')
+
+ # Remove 'display: none' from <html>
+ self.marionette.execute_script(
+ 'arguments[0].style.display = "unset";',
+ script_args=[html]
+ )
+
+ # If AccessibleCaretEventHub is initialized successfully, select a word
+ # should work.
+ self._test_long_press_to_select_a_word(content)
+
+ def test_long_press_to_select_when_partial_visible_word_is_selected(self):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, self._input_id)
+ sel = SelectionManager(el)
+
+ # To successfully select the second word while the first word is being
+ # selected, use sufficient spaces between 'a' and 'b' to avoid the
+ # second caret covers on the second word.
+ original_content = 'aaaaaaaa bbbbbbbb'
+ el.clear()
+ el.send_keys(original_content)
+ words = original_content.split()
+
+ # We cannot use self.long_press_on_word() directly since it has will
+ # change the cursor position which affects this test. We have to store
+ # the position of word 0 and word 1 before long-pressing to select the
+ # word.
+ word0_x, word0_y = self.word_location(el, 0)
+ word1_x, word1_y = self.word_location(el, 1)
+
+ self.long_press_on_location(el, word0_x, word0_y)
+ self.assertEqual(words[0], sel.selected_content)
+
+ self.long_press_on_location(el, word1_x, word1_y)
+ self.assertEqual(words[1], sel.selected_content)
+
+ self.long_press_on_location(el, word0_x, word0_y)
+ self.assertEqual(words[0], sel.selected_content)
+
+ # If the second carets is visible, it can be dragged to the position of
+ # the first caret. After that, selection will contain only the first
+ # character.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
+ self.assertEqual(words[0][0], sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_carets_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 3, 'Expect at least three words in the content.')
+
+ # Goal: the selection is not changed after dragging the caret on the
+ # Y-axis.
+ target_content = words[1]
+
+ self.long_press_on_word(el, 1)
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+
+ # Drag the first caret up by 50px.
+ self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 50).perform()
+ self.assertEqual(target_content, sel.selected_content)
+
+ # Drag the second caret down by 50px.
+ self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
+ self.assertEqual(target_content, sel.selected_content)
diff --git a/layout/base/tests/mochitest.ini b/layout/base/tests/mochitest.ini
new file mode 100644
index 000000000..279b0af8a
--- /dev/null
+++ b/layout/base/tests/mochitest.ini
@@ -0,0 +1,316 @@
+[DEFAULT]
+# Android: SLOW_DIRECTORY;
+skip-if = toolkit == 'android'
+support-files =
+ Ahem.ttf
+ border_radius_hit_testing_iframe.html
+ preserve3d_sorting_hit_testing_iframe.html
+ preserve3d_sorting_hit_testing2_iframe.html
+ image_rgrg-256x256.png
+ image_rrgg-256x256.png
+ bug369950-subframe.xml
+ file_bug842853.sjs
+ file_bug842853.html
+ ../../../dom/plugins/test/mochitest/plugin-utils.js
+ bug558663.html
+ bug956530-1.html
+ bug956530-1-ref.html
+ bug989012-1.html
+ bug989012-1-ref.html
+ bug989012-2.html
+ bug989012-2-ref.html
+ bug989012-3.html
+ bug989012-3-ref.html
+ bug1061468.html
+ bug1061468-ref.html
+ bug1097242-1.html
+ bug1097242-1-ref.html
+ bug1109968-1-ref.html
+ bug1109968-1.html
+ bug1109968-2-ref.html
+ bug1109968-2.html
+ bug1123067-1.html
+ bug1123067-2.html
+ bug1123067-3.html
+ bug1123067-ref.html
+ bug1132768-1.html
+ bug1132768-1-ref.html
+ bug1237236-1.html
+ bug1237236-1-ref.html
+ bug1237236-2.html
+ bug1237236-2-ref.html
+ bug1258308-1.html
+ bug1258308-1-ref.html
+ bug1258308-2.html
+ bug1258308-2-ref.html
+ bug1259949-1.html
+ bug1259949-1-ref.html
+ bug1259949-2.html
+ bug1259949-2-ref.html
+ bug1263288.html
+ bug1263288-ref.html
+ selection-utils.js
+ multi-range-user-select.html
+ multi-range-user-select-ref.html
+ multi-range-script-select.html
+ multi-range-script-select-ref.html
+ transformed_scrolling_repaints_3_window.html
+
+[test_preserve3d_sorting_hit_testing.html]
+[test_preserve3d_sorting_hit_testing2.html]
+[test_after_paint_pref.html]
+[test_bug370436.html]
+[test_bug993936.html]
+[test_border_radius_hit_testing.html]
+[test_bug66619.html]
+[test_bug93077-1.html]
+[test_bug93077-2.html]
+[test_bug93077-3.html]
+[test_bug93077-4.html]
+[test_bug93077-5.html]
+[test_bug93077-6.html]
+[test_bug114649.html]
+[test_bug369950.html]
+skip-if = true # Bug 492575
+[test_bug386575.xhtml]
+[test_bug388019.html]
+[test_bug394057.html]
+[test_bug399284.html]
+[test_bug399951.html]
+[test_bug404209.xhtml]
+[test_bug416896.html]
+[test_bug423523.html]
+[test_bug449781.html]
+[test_bug450930.xhtml]
+skip-if = buildapp == 'b2g' || true # bug 934301
+support-files = bug450930.xhtml
+[test_bug465448.xul]
+[test_bug469170.html]
+[test_bug471126.html]
+[test_bug435293-scale.html]
+[test_bug435293-interaction.html]
+[test_bug435293-skew.html]
+[test_reftests_with_caret.html]
+support-files =
+ bug106855-1.html
+ bug106855-2.html
+ bug106855-1-ref.html
+ bug240933-1.html
+ bug240933-2.html
+ bug240933-1-ref.html
+ bug389321-1.html
+ bug389321-1-ref.html
+ bug389321-2.html
+ bug389321-2-ref.html
+ bug389321-3.html
+ bug389321-3-ref.html
+ bug482484.html
+ bug482484-ref.html
+ bug503399.html
+ bug503399-ref.html
+ bug512295-1.html
+ bug512295-1-ref.html
+ bug512295-2.html
+ bug512295-2-ref.html
+ bug585922.html
+ bug585922-ref.html
+ bug597519-1.html
+ bug597519-1-ref.html
+ bug602141-1.html
+ bug602141-1-ref.html
+ bug602141-2.html
+ bug602141-2-ref.html
+ bug602141-3.html
+ bug602141-3-ref.html
+ bug602141-4.html
+ bug602141-4-ref.html
+ bug612271-1.html
+ bug612271-2.html
+ bug612271-3.html
+ bug612271-ref.html
+ bug613433-1.html
+ bug613433-2.html
+ bug613433-3.html
+ bug613433-ref.html
+ bug613807-1.html
+ bug613807-1-ref.html
+ bug632215-1.html
+ bug632215-2.html
+ bug632215-ref.html
+ bug633044-1.html
+ bug633044-1-ref.html
+ bug634406-1.html
+ bug634406-1-ref.html
+ bug644428-1.html
+ bug644428-1-ref.html
+ bug646382-1.html
+ bug646382-1-ref.html
+ bug646382-2.html
+ bug646382-2-ref.html
+ bug664087-1.html
+ bug664087-1-ref.html
+ bug664087-2.html
+ bug664087-2-ref.html
+ bug682712-1.html
+ bug682712-1-ref.html
+ bug746993-1.html
+ bug746993-1-ref.html
+ bug923376.html
+ bug923376-ref.html
+ bug966992-1.html
+ bug966992-1-ref.html
+ bug966992-2.html
+ bug966992-2-ref.html
+ bug966992-3.html
+ bug966992-3-ref.html
+ bug1007065-1.html
+ bug1007065-1-ref.html
+ bug1007067-1.html
+ bug1007067-1-ref.html
+ bug1082486-1.html
+ bug1082486-1-ref.html
+ bug1082486-2.html
+ bug1082486-2-ref.html
+ bug1263357-1.html
+ bug1263357-1-ref.html
+ bug1263357-2.html
+ bug1263357-2-ref.html
+ bug1263357-3.html
+ bug1263357-3-ref.html
+ bug1263357-4.html
+ bug1263357-4-ref.html
+ bug1263357-5.html
+ bug1263357-5-ref.html
+ input-maxlength-valid-before-change.html
+ input-maxlength-valid-change.html
+ input-maxlength-invalid-change.html
+ input-minlength-valid-before-change.html
+ input-minlength-valid-change.html
+ input-minlength-invalid-change.html
+ input-maxlength-ui-valid-change.html
+ input-maxlength-ui-invalid-change.html
+ input-minlength-ui-valid-change.html
+ input-minlength-ui-invalid-change.html
+ input-valid-ref.html
+ input-invalid-ref.html
+ textarea-maxlength-valid-before-change.html
+ textarea-maxlength-valid-change.html
+ textarea-maxlength-invalid-change.html
+ textarea-minlength-valid-before-change.html
+ textarea-minlength-valid-change.html
+ textarea-minlength-invalid-change.html
+ textarea-maxlength-ui-valid-change.html
+ textarea-maxlength-ui-invalid-change.html
+ textarea-minlength-ui-valid-change.html
+ textarea-minlength-ui-invalid-change.html
+ textarea-valid-ref.html
+ textarea-invalid-ref.html
+[test_bug514127.html]
+[test_bug518777.html]
+[test_bug548545.xhtml]
+[test_bug558663.html]
+[test_bug559499.html]
+[test_bug569520.html]
+[test_bug582181-1.html]
+[test_bug582181-2.html]
+[test_bug588174.html]
+[test_bug607529.html]
+support-files = file_bug607529.html
+[test_bug667512.html]
+[test_bug677878.html]
+[test_bug696020.html]
+[test_event_target_radius.html]
+[test_event_target_iframe_oop.html]
+skip-if = e10s # bug 1020135, nested oop iframes not supported
+support-files = bug921928_event_target_iframe_apps_oop.html
+[test_mozPaintCount.html]
+skip-if = toolkit == 'android'
+[test_scroll_event_ordering.html]
+[test_scroll_selection_into_view.html]
+support-files=scroll_selection_into_view_window.html
+[test_scroll_snapping.html]
+skip-if = buildapp == 'android' # bug 1041833
+[test_scroll_snapping_scrollbars.html]
+skip-if = buildapp == 'android' # bug 1041833
+[test_bug583889.html]
+support-files = bug583889_inner1.html bug583889_inner2.html
+[test_bug582771.html]
+[test_bug968148.html]
+support-files = bug968148_inner.html
+[test_bug603550.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug629838.html]
+skip-if = toolkit == 'android'
+[test_bug646757.html]
+[test_bug718809.html]
+[test_bug725426.html]
+[test_bug731777.html]
+[test_bug761572.html]
+[test_bug770106.html]
+[test_remote_frame.html]
+[test_bug842853.html]
+[test_bug842853-2.html]
+[test_bug849219.html]
+[test_bug851485.html]
+[test_bug851445.html]
+support-files = bug851445_helper.html
+[test_bug970964.html]
+support-files = bug970964_inner.html
+[test_bug976963.html]
+support-files = bug976963_inner.html
+[test_bug977003.html]
+support-files =
+ bug977003_inner_1.html
+ bug977003_inner_2.html
+ bug977003_inner_3.html
+ bug977003_inner_4.html
+ bug977003_inner_5.html
+ bug977003_inner_6.html
+[test_emulateMedium.html]
+[test_getClientRects_emptytext.html]
+[test_bug858459.html]
+
+# because of bug 469208.
+[test_bug332655-1.html]
+skip-if = toolkit == 'android'
+[test_bug332655-2.html]
+[test_bug499538-1.html]
+[test_bug749186.html]
+[test_bug644768.html]
+[test_flush_on_paint.html]
+skip-if = true || (toolkit == 'android') || (toolkit == "cocoa") # Bug 688128, bug 539356
+[test_getBoxQuads_convertPointRectQuad.html]
+[test_bug687297.html]
+support-files =
+ bug687297_a.html
+ bug687297_b.html
+ bug687297_c.html
+[test_bug990340.html]
+[test_bug1080360.html]
+support-files = bug1080360_inner.html
+[test_bug1078327.html]
+support-files = bug1078327_inner.html
+[test_bug1080361.html]
+support-files = bug1080361_inner.html
+[test_frame_reconstruction_for_pseudo_elements.html]
+[test_bug1093686.html]
+support-files = bug1093686_inner.html
+[test_bug1120705.html]
+skip-if = buildapp == 'android' || os == 'mac' || toolkit == 'gtk2' || toolkit == 'gtk3' # android does not have clickable scrollbars, mac does not have scrollbar down and up buttons, gtk may or may not have scrollbar buttons depending on theme
+[test_bug1153130.html]
+support-files = bug1153130_inner.html
+[test_bug1162990.html]
+support-files =
+ bug1162990_inner_1.html
+ bug1162990_inner_2.html
+[test_bug1226904.html]
+support-files = bug1226904.html
+[test_bug1246622.html]
+[test_bug1278021.html]
+[test_transformed_scrolling_repaints.html]
+[test_transformed_scrolling_repaints_2.html]
+[test_transformed_scrolling_repaints_3.html]
+[test_frame_reconstruction_scroll_restore.html]
+[test_resize_flush.html]
+support-files =
+ resize_flush_iframe.html
diff --git a/layout/base/tests/multi-range-script-select-ref.html b/layout/base/tests/multi-range-script-select-ref.html
new file mode 100644
index 000000000..cf42ee79b
--- /dev/null
+++ b/layout/base/tests/multi-range-script-select-ref.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+
+var sel = window.getSelection();
+var e = document.querySelector('#select');
+function setupSelectionPrev3() {
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+function setupSelectionPrev2() {
+ addChildRanges([[0,150,0,160], [0,70,0,70]], e);
+ sel.extend(e.firstChild, 65); // to get eDirPrevious direction
+}
+function setupSelectionPrev1() {
+ addChildRanges([[0,160,0,160]], e);
+ sel.extend(e.firstChild, 150); // to get eDirPrevious direction
+}
+
+function setupSelectionNext3() {
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+function setupSelectionNext2() {
+ addChildRanges([[0,10,0,15], [0,65,0,70]], e);
+}
+function setupSelectionNext2b() {
+ addChildRanges([[0,15,0,80], [0,150,0,160]], e);
+}
+function setupSelectionNext1() {
+ addChildRanges([[0,10,0,15]], e);
+}
+function setupSelectionNext1b() {
+ addChildRanges([[0,15,0,170]], e);
+}
+function setupSelectionNext1c() {
+ addChildRanges([[0,150,0,160]], e);
+}
+
+function runTest() {
+ var hash = window.location.hash
+ var op = hash.substring(6,8);
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ setupSelectionPrev3();
+ if (op == "SL") {
+ sel.extend(e.firstChild, 8);
+ } else if (op == "SR") {
+ sel.extend(e.firstChild, 12);
+ } else if (op == "AD") {
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#prev2") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#prev3") {
+ if (op == "S_") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 20);
+ } else if (op == "SA") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 20);
+ }
+ } else if (test == "#prev4") {
+ addChildRanges([[0,67,0,70], [0,150,0,160], [0,15,0,67]], e);
+ } else if (test == "#prev5") {
+ if (op == "S_") {
+ setupSelectionNext2b();
+ } else if (op == "SA") {
+ setupSelectionNext2b();
+ }
+ } else if (test == "#prev6") {
+ addChildRanges([[0,152,0,160], [0,15,0,152]], e);
+ } else if (test == "#prev7") {
+ if (op == "AD") {
+ setupSelectionPrev3();
+ addChildRanges([[0,168,0,170]], e);
+ } else if (op == "S_") {
+ setupSelectionNext1b();
+ } else if (op == "SA") {
+ setupSelectionNext1b();
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (op == "SL") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 158);
+ } else if (op == "SR") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 162);
+ } else if (op == "AD") {
+ setupSelectionNext3();
+ addChildRanges([[0,1,0,2]], e);
+ } else if (op == "S_") {
+ setupSelectionNext1c();
+ sel.extend(e.firstChild, 1);
+ } else if (op == "SA") {
+ setupSelectionNext1c();
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#next2") {
+ addChildRanges([[0,10,0,13], [0,150,0,151]], e);
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#next3") {
+ if (op == "S_") {
+ addChildRanges([[0,10,0,15], [0,150,0,151]], e);
+ sel.extend(e.firstChild, 20);
+ } else if (op == "SA") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 20);
+ }
+ } else if (test == "#next4") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 67);
+ } else if (test == "#next5") {
+ if (op == "S_") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 80);
+ } else if (op == "SA") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 80);
+ }
+ } else if (test == "#next6") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 152);
+ } else if (test == "#next7") {
+ setupSelectionNext3();
+ if (op == "AD") {
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ sel.extend(e.firstChild, 170);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-script-select.html b/layout/base/tests/multi-range-script-select.html
new file mode 100644
index 000000000..b9bcaeab8
--- /dev/null
+++ b/layout/base/tests/multi-range-script-select.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+window.info = parent.info;
+window.is = parent.is;
+window.isnot = parent.isnot;
+window.ok = parent.ok;
+
+function setupPrevSelection() {
+ var sel = window.getSelection();
+ var e = document.querySelector('#select');
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+
+function setupNextSelection() {
+ var sel = window.getSelection();
+ var e = document.querySelector('#select');
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+
+var ops = {
+ S_ : shiftClick,
+ SA : shiftAccelClick,
+ AD : accelDragSelect,
+ SL : keyLeft,
+ SR : keyRight
+}
+
+function runTest() {
+ var e = document.querySelector('#select');
+ var hash = window.location.hash
+ if (hash.substring(0,5)=="#prev")
+ setupPrevSelection();
+ else
+ setupNextSelection();
+ var op = hash.substring(6,8);
+ var action = ops[op];
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,8,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,12,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,15], [0,65,0,70], [0,150,0,160]], e);
+ }
+ } else if (test == "#prev2") {
+ action(e, 260);
+ checkRanges([[0,13,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev3") {
+ action(e, 400);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,20], [0,65,0,70], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,20], [0,65,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev4") {
+ action(e, 180, 65);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,67], [0,67,0,70], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,67], [0,67,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev5") {
+ action(e, 440, 65);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,80], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,80], [0,150,0,160]], e);
+ } else if (test == "#prev6") {
+ action(e, 140, 125);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,152], [0,152,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,152], [0,152,0,160]], e);
+ } else if (test == "#prev7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160], [0,168,0,170]], e);
+ } else if (action == shiftClick) {
+ action(e, 500, 125);
+ checkRanges([[0,15,0,170]], e);
+ } else if (action == shiftAccelClick) {
+ action(e, 500, 125);
+ checkRanges([[0,15,0,170]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,158]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,162]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,150]], e);
+ }
+ } else if (test == "#next2") {
+ action(e, 260);
+ checkRanges([[0,10,0,13], [0,13,0,150]], e);
+ } else if (test == "#next3") {
+ action(e, 400);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,20,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,20,0,150]], e);
+ } else if (test == "#next4") {
+ action(e, 180, 65);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,67], [0,67,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,67], [0,67,0,150]], e);
+ } else if (test == "#next5") {
+ action(e, 440, 65);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,80,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,80,0,150]], e);
+ } else if (test == "#next6") {
+ action(e, 140, 125);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,152]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,152]], e);
+ } else if (test == "#next7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160], [0,168,0,170]], e);
+ } else if (action == shiftClick) {
+ action(e, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,170]], e);
+ } else if (action == shiftAccelClick) {
+ action(e, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,170]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-user-select-ref.html b/layout/base/tests/multi-range-user-select-ref.html
new file mode 100644
index 000000000..812bae047
--- /dev/null
+++ b/layout/base/tests/multi-range-user-select-ref.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+
+var sel = window.getSelection();
+var e = document.querySelector('#select');
+function setupSelectionPrev3() {
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+function setupSelectionPrev2() {
+ addChildRanges([[0,150,0,160], [0,70,0,70]], e);
+ sel.extend(e.firstChild, 65); // to get eDirPrevious direction
+}
+function setupSelectionPrev1() {
+ addChildRanges([[0,160,0,160]], e);
+ sel.extend(e.firstChild, 150); // to get eDirPrevious direction
+}
+
+function setupSelectionNext3() {
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+function setupSelectionNext2() {
+ addChildRanges([[0,10,0,15], [0,65,0,70]], e);
+}
+function setupSelectionNext2b() {
+ addChildRanges([[0,15,0,80], [0,150,0,160]], e);
+}
+function setupSelectionNext1() {
+ addChildRanges([[0,10,0,15]], e);
+}
+function setupSelectionNext1b() {
+ addChildRanges([[0,15,0,170]], e);
+}
+function setupSelectionNext1c() {
+ addChildRanges([[0,150,0,160]], e);
+}
+
+function runTest() {
+ sel = window.getSelection();
+ sel.removeAllRanges();
+ document.body.offsetHeight;
+ var hash = window.location.hash
+ var op = hash.substring(6,8);
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ setupSelectionPrev3();
+ if (op == "SL") {
+ sel.extend(e.firstChild, 8);
+ } else if (op == "SR") {
+ sel.extend(e.firstChild, 12);
+ } else if (op == "AD") {
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#prev2") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 14); // now eDirNext
+ sel.extend(e.firstChild, 13); // now eDirPrevious again
+ } else if (test == "#prev3") {
+ setupSelectionPrev2();
+ sel.extend(e.firstChild, 20);
+ } else if (test == "#prev4") {
+ setupSelectionPrev2();
+ sel.extend(e.firstChild, 68); // now eDirNext
+ sel.extend(e.firstChild, 67); // now eDirPrevious again
+ } else if (test == "#prev5") {
+ setupSelectionPrev1();
+ sel.extend(e.firstChild, 80);
+ } else if (test == "#prev6") {
+ setupSelectionPrev1();
+ sel.extend(e.firstChild, 153); // now eDirNext
+ sel.extend(e.firstChild, 152); // now eDirPrevious again
+ } else if (test == "#prev7") {
+ if (op == "AD") {
+ setupSelectionPrev3();
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ addChildRanges([[0,160,0,170]], e);
+ }
+ } else if (test == "#prev8") {
+ if (op == "AD") {
+ addChildRanges([[0,150,0,155], [0,68,0,70]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (op == "SL") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 158);
+ } else if (op == "SR") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 162);
+ } else if (op == "AD") {
+ setupSelectionNext3();
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#next2") {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#next3") {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 20);
+ } else if (test == "#next4") {
+ setupSelectionNext2();
+ sel.extend(e.firstChild, 67);
+ } else if (test == "#next5") {
+ setupSelectionNext2();
+ sel.extend(e.firstChild, 80);
+ } else if (test == "#next6") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 152);
+ } else if (test == "#next7") {
+ setupSelectionNext3();
+ if (op == "AD") {
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ sel.extend(e.firstChild, 170);
+ }
+ } else if (test == "#next8") {
+ if (op == "AD") {
+ addChildRanges([[0,68,0,70], [0,150,0,155]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(function(){setTimeout(runTest,0)});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-user-select.html b/layout/base/tests/multi-range-user-select.html
new file mode 100644
index 000000000..9da8f9398
--- /dev/null
+++ b/layout/base/tests/multi-range-user-select.html
@@ -0,0 +1,223 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+span { -moz-user-select:none; }
+x { -moz-user-select:text; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-a<x>k7e-1</x><span id="span2">1e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae</span><x>4-ci5</x><span id="span3">c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a9631</span><x>9c8-ad7d-1</x>1e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+window.info = parent.info;
+window.is = parent.is;
+window.isnot = parent.isnot;
+window.ok = parent.ok;
+
+var sel = window.getSelection();
+
+function enableSelection(id) {
+ var span = document.getElementById(id);
+ span.style.MozUserSelect = 'text';
+}
+
+function setupPrevSelection() {
+ var e = document.querySelector('#select');
+ dragSelectPoints(e, 300, 125, 200, 5);
+}
+
+function setupNextSelection() {
+ var e = document.querySelector('#select');
+ dragSelectPoints(e, 199, 5, 300, 125);
+}
+
+var ops = {
+ S_ : shiftClick,
+ SA : shiftAccelClick,
+ AD : accelDragSelect,
+ SL : keyLeft,
+ SR : keyRight
+}
+
+function runTest() {
+ sel = window.getSelection();
+ sel.removeAllRanges();
+ document.body.offsetHeight;
+ var e = document.querySelector('#select');
+ var hash = window.location.hash
+ if (hash.substring(0,5)=="#prev")
+ setupPrevSelection();
+ else
+ setupNextSelection();
+ var op = hash.substring(6,8);
+ var action = ops[op];
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,8,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[e.childNodes[1].firstChild,2,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [e.childNodes[1].firstChild,0,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ }
+ } else if (test == "#prev2") {
+ action(e, 260);
+ checkRangeCount(3, e);
+ checkRange(0, [0,3,-2,2], e.childNodes[1]);
+ checkRange(1, [3,0,-1,4], e);
+ checkRange(2, [5,0,6,0], e);
+ } else if (test == "#prev3") {
+ enableSelection('span2');
+ action(e, 400);
+ checkRangeCount(2, e);
+ checkRange(0, [0,5,-2,4], e.childNodes[2]);
+ checkRange(1, [5,0,6,0], e);
+ } else if (test == "#prev4") {
+ action(e, 180, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,2,-2,4], e.childNodes[3]);
+ checkRange(1, [5,0,6,0], e);
+ } else if (test == "#prev5") {
+ enableSelection('span3');
+ action(e, 440, 65);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[4].firstChild,10,e.childNodes[6],0], e);
+ } else if (test == "#prev6") {
+ action(e, 140, 125);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[5].firstChild,2,e.childNodes[6],0], e);
+ } else if (test == "#prev7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[e.childNodes[1].firstChild,0,-1,2], [3,0,-1,4], [5,0,6,0], [6,8,6,10]], e);
+ } else {
+ action(e, 500, 125);
+ checkRanges([[6,0,6,10]], e);
+ }
+ } else if (test == "#prev8") {
+ if (action == accelDragSelect) {
+ sel.removeAllRanges();
+ var e = document.querySelector('#select');
+ synthesizeMouse(e, 200, 125, {type: "mousedown", accelKey: true});
+ synthesizeMouse(e, 200, 120, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 100, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 80, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 210, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 60, {type: "mouseup", accelKey: true});
+ var x3t = e.childNodes[3].firstChild;
+ var x5 = e.childNodes[5];
+ checkRanges([[x3t,3,-1,4], [x5,0,x5.firstChild,5]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,8]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,6,2]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,10]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,10]], e);
+ }
+ } else if (test == "#next2") {
+ action(e, 260);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[0],10,e.childNodes[1].firstChild,3], e);
+ } else if (test == "#next3") {
+ enableSelection('span2');
+ action(e, 400);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[0],10,e.childNodes[2].firstChild,5], e);
+ } else if (test == "#next4") {
+ action(e, 180, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [-1,0,0,2], e.childNodes[3]);
+ } else if (test == "#next5") {
+ enableSelection('span3');
+ action(e, 440, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRangePoints(1, [e.childNodes[3],0,e.childNodes[4].firstChild,10], e);
+ } else if (test == "#next6") {
+ action(e, 140, 125);
+ checkRangeCount(3, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [3,0,-1,4], e);
+ checkRange(2, [-1,0,0,2], e.childNodes[5]);
+ } else if (test == "#next7") {
+ if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,6,2]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,10], [6,8,6,10]], e);
+ } else {
+ action(e, 500, 125);
+ checkRangeCount(3, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [3,0,-1,4], e);
+ checkRangePoints(2, [e.childNodes[5],0,e.childNodes[6],10], e);
+ }
+ } else if (test == "#next8") {
+ if (action == accelDragSelect) {
+ sel.removeAllRanges();
+ var e = document.querySelector('#select');
+ synthesizeMouse(e, 200, 60, {type: "mousedown", accelKey: true});
+ synthesizeMouse(e, 180, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 80, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 100, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 120, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 190, 125, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 125, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 125, {type: "mouseup", accelKey: true});
+ var x3t = e.childNodes[3].firstChild;
+ var x5 = e.childNodes[5];
+ checkRanges([[x3t,3,-1,4], [x5,0,x5.firstChild,5]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(function(){setTimeout(runTest,0)});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html b/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html
new file mode 100644
index 000000000..e9fa71977
--- /dev/null
+++ b/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+body {
+ background: #333;
+ overflow: hidden;
+}
+
+::-webkit-scrollbar {
+ display: none;
+}
+
+div {
+ margin: 0;
+ padding: 0;
+ -webkit-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+ position: absolute;
+}
+
+#container {
+ font-family: UnifrakturMaguntia;
+ width: 350px;
+ height: 70%;
+ max-height: 500px;
+ -webkit-perspective: 5000px;
+ perspective: 5000px;
+ transform: translate(-50%, -50%) rotateY(20deg);
+}
+
+#container p {
+ padding: 0 5px 0 5px;
+}
+
+#container hr {
+ margin: 0 20px 0 20px;
+}
+
+#content {
+ -ms-overflow-style: none;
+ overflow: -moz-scrollbars-none;
+ overflow-y: scroll;
+ height: 100%;
+ background: #fefee0;
+}
+
+
+#lorem {
+ font-size: 7em;
+ float: left;
+ color: red;
+ border: 1px solid black;
+ margin-right: 5px;
+}
+
+#tree {
+ float: right;
+ width: 10em;
+ height: 10em;
+ border: 1px solid black;
+ margin: 0 5px 0 2px;
+}
+</style>
+</head>
+<body>
+ <div id="container">
+ <div id="content">
+ <p>
+ <span id="lorem">L</span>orem ipsum dolor sit amet, consectetur adipiscing elit. Integer sagittis nisi urna, a ultrices est facilisis a. Morbi porttitor vulputate odio, eu lacinia nisi. Suspendisse felis sapien, facilisis nec ex in, blandit tincidunt tellus. Sed at commodo nunc. In nibh lectus, facilisis nec magna nec, bibendum egestas nunc. Nam varius lorem in fringilla cursus. Integer dignissim, lectus vitae sodales molestie, libero purus malesuada arcu, vitae facilisis nunc dolor non mi. In nunc tortor, tempor non pharetra vitae, mattis a purus. Nulla rhoncus vitae metus vel ornare. Nunc augue dui, suscipit ac urna vel, consectetur volutpat ipsum. Nunc ac nulla ut enim laoreet placerat. Sed luctus aliquam purus, sollicitudin blandit dui blandit id. Aenean venenatis risus dolor, at viverra urna aliquam non. Morbi sit amet pellentesque justo, eget viverra augue.
+ </p>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Praesent posuere ultricies orci sit amet lacinia. Suspendisse lacinia scelerisque risus, sodales egestas turpis cursus sed. Proin sed mollis mauris, vitae ultricies nibh. Nulla bibendum leo a mauris luctus, sit amet iaculis arcu blandit. Etiam pulvinar, odio et rutrum egestas, elit mi maximus ex, id elementum est tortor id turpis. Duis rhoncus et lorem vel maximus. Aenean at justo sagittis, aliquet eros eget, iaculis magna. Nam non orci congue, dapibus dui eget, sagittis nisl. Phasellus venenatis id est et tempor. Aenean condimentum tristique nibh sit amet varius. Vestibulum et lectus quis eros dapibus consectetur nec auctor dolor. Sed euismod eu felis aliquam fermentum. Donec lacinia fringilla erat, at eleifend velit tempus at.
+ </p>
+ <hr>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Cras justo turpis, vulputate eget venenatis sit amet, bibendum quis dolor. Cras at interdum libero. Quisque convallis rutrum magna in ultrices. Donec ut magna dolor. Mauris pulvinar ut sapien a posuere. Sed nisi elit, tincidunt vitae magna eu, dapibus suscipit purus. Maecenas tincidunt mollis eros et dictum. Duis est nulla, rhoncus tincidunt velit at, venenatis elementum velit. Phasellus lobortis sem tellus, id sodales quam dignissim nec. Phasellus pulvinar metus ex, nec gravida nunc elementum vel. Ut mattis varius fringilla. Phasellus imperdiet sit amet risus a elementum. Donec pulvinar ante sit amet massa blandit ullamcorper. Donec vitae malesuada nisl, et laoreet sem.
+ </p>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Suspendisse bibendum elit blandit arcu vulputate, nec hendrerit dui vehicula. Vestibulum porta finibus odio vitae maximus. Duis in vulputate risus. Donec mattis turpis ex, vitae semper sem ultrices eu. Aliquam in ex blandit erat ultrices sollicitudin. Vestibulum porta nisl in porttitor rutrum. Integer consectetur porttitor ligula facilisis malesuada. Proin placerat enim sed lacus commodo mollis nec eu arcu. In hac habitasse platea dictumst. Curabitur luctus est risus, sit amet fringilla nunc condimentum vel. Integer mauris lorem, molestie ut nisl sit amet, pellentesque mollis quam. Aliquam posuere purus non nisi molestie semper.
+ </p>
+ <hr>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris facilisis nisi diam, eu pulvinar ex sollicitudin sed. Maecenas sed eros id quam suscipit ultricies ut tincidunt quam. Donec iaculis, justo at fringilla laoreet, quam sem dapibus urna, ut eleifend odio eros et ligula. Proin urna ante, condimentum vitae sollicitudin sit amet, egestas ac nunc. Aenean sapien velit, porta a eros quis, iaculis dignissim felis. Suspendisse mollis vulputate metus vel interdum. Aliquam hendrerit elementum erat, sit amet commodo velit suscipit et. Sed semper sem at mauris rhoncus, id efficitur arcu molestie. Nam feugiat lorem pretium, consectetur felis et, fringilla dolor. Nunc dui velit, elementum non hendrerit nec, sagittis vitae odio. Curabitur nec leo tincidunt, pellentesque metus at, condimentum risus.
+ </p>
+ </div>
+ </div>
+</body>
+
+<script type="application/javascript">
+ window.onload = function() {
+ opener.child_opened(document);
+ };
+</script>
+
+</html>
diff --git a/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html b/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
new file mode 100644
index 000000000..01e605911
--- /dev/null
+++ b/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<title>preserve-3d hit testing</title>
+<style>
+ #stage {
+ }
+
+ #parent {
+ -moz-transform-style: preserve-3d;
+ }
+
+ #big {
+ width: 1000px;
+ height: 1000px;
+ background-color: #995C7F;
+ -moz-transform: translate3d(0px, 0px, 350px) rotatey(45deg);
+ }
+
+ #small {
+ width: 100px;
+ height: 100px;
+ background-color: #835A99;
+ -moz-transform: translate3d(600px, 200px, 350px);
+ }
+
+</style>
+<body>
+ <div id="stage">
+ <div id="parent">
+ <div id="small"></div>
+ <div id="big"></div>
+ </div>
+ </div>
diff --git a/layout/base/tests/resize_flush_iframe.html b/layout/base/tests/resize_flush_iframe.html
new file mode 100644
index 000000000..37555a514
--- /dev/null
+++ b/layout/base/tests/resize_flush_iframe.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <style>
+ body {
+ background-color: red;
+ }
+
+ @media (min-width: 250px) {
+ body {
+ background-color: green;
+ }
+ }
+ </style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/base/tests/scroll_selection_into_view_window.html b/layout/base/tests/scroll_selection_into_view_window.html
new file mode 100644
index 000000000..46fbb0d19
--- /dev/null
+++ b/layout/base/tests/scroll_selection_into_view_window.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scrolling selection into view</title>
+</head>
+<body onload="opener.doTest();">
+
+<div id="c1" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:0;">
+ <div style="height:400px;"></div>
+ <div><span id="target1"
+ style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c2" style="overflow:hidden; width:200px; height:200px; position:absolute; top:200px; left:200px;">
+ <div style="height:400px;"></div>
+ <div><span id="target2"
+ style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c3" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:400px;">
+ <div style="height:400px;"></div>
+ <div><span id="target3"
+ style="display:inline-block; vertical-align:top; height:300px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c4" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:600px;">
+ <iframe id="target4" style="border:none; width:100%; height:1100px; display:block;"
+ src="data:text/html,
+ <body style='margin:0; overflow:hidden;'>
+ <div style='height:400px;'></div>
+ <div><span id='target4'
+ style='display:inline-block; vertical-align:top; height:300px;'></span>
+ </div>
+ <div style='height:400px;'></div>">
+ </iframe>
+</div>
+<div id="c5" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:0;">
+ <div style="-moz-transform:translateY(400px); transform:translateY(400px)">
+ <span id="target5" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+<div id="c6" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:200px;">
+ <div style="height:200px"></div>
+ <div style="height:100px; -moz-transform:scale(2); transform:scale(2)">
+ <span id="target6" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+<div id="c7" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:400px;">
+ <div style="overflow:auto; height:200px; -moz-transform:translateY(400px); transform:translateY(400px)">
+ <div style="height:200px;"></div>
+ <div>
+ <span id="target7" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+
+</body>
+
+</html>
diff --git a/layout/base/tests/selection-utils.js b/layout/base/tests/selection-utils.js
new file mode 100644
index 000000000..a14ff0d81
--- /dev/null
+++ b/layout/base/tests/selection-utils.js
@@ -0,0 +1,154 @@
+// -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
+// vim: set ts=2 sw=2 et tw=78:
+function clearSelection(w)
+{
+ var sel = (w ? w : window).getSelection();
+ sel.removeAllRanges();
+}
+
+function getNode(e, index) {
+ if (!(typeof index==='number')) return index;
+ if (index >= 0) return e.childNodes[index];
+ while (++index != 0) e = e.parentNode;
+ return e;
+}
+
+function dragSelectPointsWithData()
+{
+ var event = arguments[0];
+ var e = arguments[1];
+ var x1 = arguments[2];
+ var y1 = arguments[3];
+ var x2 = arguments[4];
+ var y2 = arguments[5];
+ dir = x2 > x1 ? 1 : -1;
+ event['type'] = "mousedown";
+ synthesizeMouse(e, x1, y1, event);
+ event['type'] = "mousemove";
+ synthesizeMouse(e, x1 + dir, y1, event);
+ for (var i = 6; i < arguments.length; ++i)
+ synthesizeMouse(e, arguments[i], y1, event);
+ synthesizeMouse(e, x2 - dir, y2, event);
+ event['type'] = "mouseup";
+ synthesizeMouse(e, x2, y2, event);
+}
+
+function dragSelectPoints()
+{
+ var args = Array.prototype.slice.call(arguments);
+ dragSelectPointsWithData.apply(this, [{}].concat(args));
+}
+
+function dragSelect()
+{
+ var args = Array.prototype.slice.call(arguments);
+ var e = args.shift();
+ var x1 = args.shift();
+ var x2 = args.shift();
+ dragSelectPointsWithData.apply(this, [{},e,x1,5,x2,5].concat(args));
+}
+
+function accelDragSelect()
+{
+ var args = Array.prototype.slice.call(arguments);
+ var e = args.shift();
+ var x1 = args.shift();
+ var x2 = args.shift();
+ var y = args.length != 0 ? args.shift() : 5;
+ dragSelectPointsWithData.apply(this, [{accelKey: true},e,x1,y,x2,y].concat(args));
+}
+
+function shiftClick(e, x, y)
+{
+ function pos(p) { return (typeof p === "undefined") ? 5 : p }
+ synthesizeMouse(e, pos(x), pos(y), { shiftKey: true });
+}
+
+function keyRepeat(key,data,repeat)
+{
+ while (repeat-- > 0)
+ synthesizeKey(key, data);
+}
+
+function keyRight(data)
+{
+ var repeat = arguments.length > 1 ? arguments[1] : 1;
+ keyRepeat("VK_RIGHT", data, repeat);
+}
+
+function keyLeft(data)
+{
+ var repeat = arguments.length > 1 ? arguments[1] : 1;
+ keyRepeat("VK_LEFT", data, repeat);
+}
+
+function shiftAccelClick(e, x, y)
+{
+ function pos(p) { return (typeof p === "undefined") ? 5 : p }
+ synthesizeMouse(e, pos(x), pos(y), { shiftKey: true, accelKey: true });
+}
+
+function accelClick(e, x, y)
+{
+ function pos(p) { return (typeof p === "undefined") ? 5 : p }
+ synthesizeMouse(e, pos(x), pos(y), { accelKey: true });
+}
+
+function addChildRanges(arr, e)
+{
+ var sel = window.getSelection();
+ for (i = 0; i < arr.length; ++i) {
+ var data = arr[i];
+ var r = new Range()
+ r.setStart(getNode(e, data[0]), data[1]);
+ r.setEnd(getNode(e, data[2]), data[3]);
+ sel.addRange(r);
+ }
+}
+
+function checkText(text, e)
+{
+ var sel = window.getSelection();
+ is(sel.toString(), text, e.id + ": selected text")
+}
+
+function checkRangeText(text, index)
+{
+ var r = window.getSelection().getRangeAt(index);
+ is(r.toString(), text, e.id + ": range["+index+"].toString()")
+}
+
+function checkRangeCount(n, e)
+{
+ var sel = window.getSelection();
+ is(sel.rangeCount, n, e.id + ": Selection range count");
+}
+
+function checkRangePoints(i, expected, e) {
+ var sel = window.getSelection();
+ if (i >= sel.rangeCount) return;
+ var r = sel.getRangeAt(i);
+ is(r.startContainer, expected[0], e.id + ": range.startContainer");
+ is(r.startOffset, expected[1], e.id + ": range.startOffset");
+ is(r.endContainer, expected[2], e.id + ": range.endContainer");
+ is(r.endOffset, expected[3], e.id + ": range.endOffset");
+}
+
+function checkRange(i, expected, e) {
+ var sel = window.getSelection();
+ if (i >= sel.rangeCount) return;
+ var r = sel.getRangeAt(i);
+ is(r.startContainer, getNode(e, expected[0]), e.id + ": range["+i+"].startContainer");
+ is(r.startOffset, expected[1], e.id + ": range["+i+"].startOffset");
+ is(r.endContainer, getNode(e, expected[2]), e.id + ": range["+i+"].endContainer");
+ is(r.endOffset, expected[3], e.id + ": range["+i+"].endOffset");
+}
+
+function checkRanges(arr, e)
+{
+ checkRangeCount(arr.length, e);
+ for (i = 0; i < arr.length; ++i) {
+ var expected = arr[i];
+ checkRange(i, expected, e);
+ }
+}
diff --git a/layout/base/tests/test_after_paint_pref.html b/layout/base/tests/test_after_paint_pref.html
new file mode 100644
index 000000000..33a8c7162
--- /dev/null
+++ b/layout/base/tests/test_after_paint_pref.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=608030
+-->
+<head>
+ <title>Test for MozAfterPaint pref Bug 608030</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=608030">Mozilla Bug 608030</a>
+<div id="display" style="width: 10em; height: 5em; background-color: red"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 608030 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+window.addEventListener("load", step0, false);
+
+is(SpecialPowers.getBoolPref("dom.send_after_paint_to_content"), true, "pref defaults to true in mochitest harness");
+
+function print_rect(rect) {
+ return "(top=" + rect.top + ",left=" + rect.left + ",width=" + rect.width + ",height=" + rect.height + ")";
+}
+
+function print_event(event) {
+ var res = "boundingClientRect=" + print_rect(event.boundingClientRect);
+ var rects = event.clientRects;
+ for (var i = 0; i < rects.length; ++i) {
+ res += " clientRects[" + i + "]=" + print_rect(rects[i]);
+ }
+ return res;
+}
+
+function step0(event) {
+ // Wait until we get the MozAfterPaint following the load event
+ // before starting.
+ ok(true, "loaded");
+ window.addEventListener("MozAfterPaint", step1, false);
+
+ // Ensure a MozAfterPaint event is fired
+ div.style.backgroundColor = "yellow";
+}
+
+var start;
+var div = document.getElementById("display");
+
+function step1(event)
+{
+ ok(true, "step1 reached: " + print_event(event));
+ window.removeEventListener("MozAfterPaint", step1, false);
+
+ start = Date.now();
+
+ window.addEventListener("MozAfterPaint", step2, false);
+
+ div.style.backgroundColor = "blue";
+}
+
+function step2(event)
+{
+ ok(true, "step2 reached: " + print_event(event));
+ window.removeEventListener("MozAfterPaint", step2, false);
+
+ var end = Date.now();
+ var timeout = 3 * Math.max(end - start, 300);
+
+ ok(true, "got MozAfterPaint (timeout for next step is " + timeout + "ms)");
+
+ // Set the pref for our second test
+
+ // When there was previously another page in our window, we seem to
+ // get duplicate events, simultaneously, so we need to register our
+ // next listener after a zero timeout.
+ SpecialPowers.pushPrefEnv({'set': [['dom.send_after_paint_to_content', false]]}, function() {setTimeout(step3, 0, timeout);});
+}
+function step3(timeout)
+{
+ ok(true, "step3 reached");
+ window.addEventListener("MozAfterPaint", failstep, false);
+
+ div.style.backgroundColor = "fuchsia";
+
+ setTimeout(step4, timeout);
+}
+
+function failstep(event)
+{
+ ok(true, "failstep reached: " + print_event(event));
+ ok(false, "got MozAfterPaint when we should not have");
+}
+
+function step4()
+{
+ ok(true, "step4 reached"); // If we didn't get the failure in failstep,
+ // then we passed.
+
+ window.removeEventListener("MozAfterPaint", failstep, false);
+
+ // Set the pref back in its initial state.
+ SpecialPowers.pushPrefEnv({'set': [['dom.send_after_paint_to_content', true]]}, SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_border_radius_hit_testing.html b/layout/base/tests/test_border_radius_hit_testing.html
new file mode 100644
index 000000000..abb7a07db
--- /dev/null
+++ b/layout/base/tests/test_border_radius_hit_testing.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595652
+-->
+<head>
+ <title>Test for Bug 595652</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=595652">Mozilla Bug 595652</a>
+<iframe src="border_radius_hit_testing_iframe.html" id="iframe" height="600" width="500" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595652 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+
+ var doc = iframe.contentDocument;
+
+ var container = doc.body;
+ var one = doc.getElementById("one");
+ var two = doc.getElementById("two").firstChild;
+
+ //container.addEventListener("click", function(event) { alert(event.clientX + "," + event.clientY) }, false);
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y), expected_element,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(42, 6, container, "outside top left corner of #one");
+ check(11, 21, container, "outside top left corner of #one");
+ check(476, 10, container, "outside top right corner of #one");
+ check(495, 28, container, "outside top right corner of #one");
+ check(483, 197, container, "outside bottom right corner of #one");
+ check(497, 175, container, "outside bottom right corner of #one");
+ check(29, 182, container, "outside bottom left corner of #one");
+ check(73, 198, container, "outside bottom left corner of #one");
+
+ check(95, 2, one, "inside top left corner of #one (curved quadrant)");
+ check(16, 27, one, "inside top left corner of #one (curved quadrant)");
+ check(87, 37, one, "inside top left corner of #one (curved quadrant)");
+ check(465, 10, one, "inside top right corner of #one (curved quadrant)");
+ check(489, 33, one, "inside top right corner of #one (curved quadrant)");
+ check(443, 56, one, "inside top right corner of #one (curved quadrant)");
+ check(493, 167, one, "inside bottom right corner of #one (curved quadrant)");
+ check(476, 188, one, "inside bottom right corner of #one (curved quadrant)");
+ check(462, 144, one, "inside bottom right corner of #one (curved quadrant)");
+ check(74, 186, one, "inside bottom left corner of #one (curved quadrant)");
+ check(16, 153, one, "inside bottom left corner of #one (curved quadrant)");
+ check(112, 124, one, "inside bottom left corner of #one (curved quadrant)");
+
+ check(250, 1, one, "along top edge of #one");
+ check(250, 199, one, "along bottom edge of #one");
+ check(1, 100, one, "along left edge of #one");
+ check(499, 100, one, "along right edge of #one");
+ check(250, 100, one, "in center of #one");
+
+ check(2, 52, one, "inside top left corner of #one (left edge, outside ellipse)");
+ check(82, 52, one, "inside top left corner of #one (left edge, inside ellipse)");
+
+ check(42, 306, container, "outside top left corner of #two");
+ check(11, 321, container, "outside top left corner of #two");
+ check(476, 310, container, "outside top right corner of #two");
+ check(495, 328, container, "outside top right corner of #two");
+ check(483, 497, container, "outside bottom right corner of #two");
+ check(497, 475, container, "outside bottom right corner of #two");
+ check(29, 482, container, "outside bottom left corner of #two");
+ check(73, 498, container, "outside bottom left corner of #two");
+
+ check(95, 302, two, "inside top left corner of #two (curved quadrant)");
+ check(16, 327, two, "inside top left corner of #two (curved quadrant)");
+ check(87, 337, two, "inside top left corner of #two (curved quadrant)");
+ check(465, 310, two, "inside top right corner of #two (curved quadrant)");
+ check(489, 333, two, "inside top right corner of #two (curved quadrant)");
+ check(443, 356, two, "inside top right corner of #two (curved quadrant)");
+ check(493, 467, two, "inside bottom right corner of #two (curved quadrant)");
+ check(476, 488, two, "inside bottom right corner of #two (curved quadrant)");
+ check(462, 444, two, "inside bottom right corner of #two (curved quadrant)");
+ check(74, 486, two, "inside bottom left corner of #two (curved quadrant)");
+ check(16, 453, two, "inside bottom left corner of #two (curved quadrant)");
+ check(112, 424, two, "inside bottom left corner of #two (curved quadrant)");
+
+ check(250, 301, two, "along top edge of #two");
+ check(250, 499, two, "along bottom edge of #two");
+ check(1, 400, two, "along left edge of #two");
+ check(499, 400, two, "along right edge of #two");
+ check(250, 400, two, "in center of #two");
+
+ check(2, 352, two, "inside top left corner of #two (left edge, outside ellipse)");
+ check(82, 352, two, "inside top left corner of #two (left edge, inside ellipse)");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1078327.html b/layout/base/tests/test_bug1078327.html
new file mode 100644
index 000000000..ed0bc3bec
--- /dev/null
+++ b/layout/base/tests/test_bug1078327.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1078327
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1078327</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(startTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1078327_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1080360.html b/layout/base/tests/test_bug1080360.html
new file mode 100644
index 000000000..f51d0b521
--- /dev/null
+++ b/layout/base/tests/test_bug1080360.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080360
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080360</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(startTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1080360_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1080361.html b/layout/base/tests/test_bug1080361.html
new file mode 100644
index 000000000..91d42ddce
--- /dev/null
+++ b/layout/base/tests/test_bug1080361.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080361
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080361</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(startTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1080361_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1093686.html b/layout/base/tests/test_bug1093686.html
new file mode 100644
index 000000000..7b63f0ba5
--- /dev/null
+++ b/layout/base/tests/test_bug1093686.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1093686
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Testing effect of listener on body with respect to event retargeting</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnEventRetargeting(startTest);
+ }
+ function turnOnEventRetargeting(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 8],
+ ["ui.mouse.radius.topmm", 12],
+ ["ui.mouse.radius.rightmm", 8],
+ ["ui.mouse.radius.bottommm", 4]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1093686_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1120705.html b/layout/base/tests/test_bug1120705.html
new file mode 100644
index 000000000..c3cc343ac
--- /dev/null
+++ b/layout/base/tests/test_bug1120705.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1120705</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<p id="display"></p>
+<select id="sel">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="10">10</option>
+ <option value="11">11</option>
+ <option value="12">12</option>
+ <option value="13">13</option>
+ <option value="14">14</option>
+ <option value="15">15</option>
+ <option value="16">16</option>
+ <option value="17">17</option>
+ <option value="18">18</option>
+ <option value="19">19</option>
+ <option value="20">20</option>
+</select>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function clickDownButton() {
+ var sel = document.getElementById("sel");
+ var scrollbar_width = sel.offsetWidth - sel.clientWidth;
+ synthesizeMouse(sel,
+ sel.offsetWidth - scrollbar_width / 2,
+ sel.offsetHeight - scrollbar_width / 2,
+ { type: "mousedown" });
+
+ synthesizeMouse(sel,
+ sel.offsetWidth - scrollbar_width / 2,
+ sel.offsetHeight - scrollbar_width / 2,
+ { type: "mouseup" });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["general.smoothScroll", false],
+ ["toolkit.scrollbox.verticalScrollDistance", 3]]},
+ function() {
+ var sel = document.getElementById("sel");
+ sel.size = 2;
+ sel.scrollTo(0, 0);
+ clickDownButton();
+ window.requestAnimationFrame(function() {
+ var restricted_top = sel.scrollTop;
+ isnot(restricted_top, 0,
+ "Expected to scroll when clicking scrollbar button");
+ sel.size = 10;
+ sel.scrollTo(0, 0);
+ clickDownButton();
+ window.requestAnimationFrame(function() {
+ isnot(sel.scrollTop, restricted_top,
+ "Scrollbar button scrolling should be limited by page size");
+ SimpleTest.finish();
+ });
+ });
+ });
+});
+</script>
+</pre>
+</body>
+
+</html>
+
diff --git a/layout/base/tests/test_bug114649.html b/layout/base/tests/test_bug114649.html
new file mode 100644
index 000000000..ac11c2aba
--- /dev/null
+++ b/layout/base/tests/test_bug114649.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=114649
+-->
+<head>
+ <title>Test for Bug 114649</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=114649">Mozilla Bug 114649</a>
+<iframe id="display" style="width: 500px; height: 500px;"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 114649 **/
+
+var gIFrame;
+var gCurrentWidth = 500;
+var gGotEventsAt = [];
+var gInterval;
+
+function run() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ gIFrame = document.getElementById("display");
+
+ var subdoc = gIFrame.contentDocument;
+ subdoc.open();
+ subdoc.write("<body onresize='window.parent.handle_child_resize()'>");
+ subdoc.close();
+
+ gInterval = window.setInterval(do_a_resize, 50);
+}
+
+function do_a_resize()
+{
+ // decrease the width by 10 until we hit 400, then stop
+ gCurrentWidth -= 10;
+ gIFrame.style.width = gCurrentWidth + "px";
+
+ if (gCurrentWidth == 400) {
+ window.clearInterval(gInterval);
+ window.setTimeout(check_for_resize_events, 250);
+ return;
+ }
+}
+
+function handle_child_resize()
+{
+ gGotEventsAt.push(gCurrentWidth);
+}
+
+function check_for_resize_events()
+{
+ ok(gGotEventsAt.length >= 2, "got continuous events");
+ isnot(gGotEventsAt[0], 400, "got continuous events");
+ is(gGotEventsAt[gGotEventsAt.length - 1], 400, "got last event");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1153130.html b/layout/base/tests/test_bug1153130.html
new file mode 100644
index 000000000..c5482629e
--- /dev/null
+++ b/layout/base/tests/test_bug1153130.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1153130</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(startTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1153130_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1162990.html b/layout/base/tests/test_bug1162990.html
new file mode 100644
index 000000000..1b6c59851
--- /dev/null
+++ b/layout/base/tests/test_bug1162990.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe;
+ var number = 0;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(finishTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function finishTest() {
+ // Try to run several tests named as bug1162990_inner_<number>.html
+ if(++number < 3)
+ iframe.src = "bug1162990_inner_" + number + ".html";
+ else
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1226904.html b/layout/base/tests/test_bug1226904.html
new file mode 100644
index 000000000..caac899ab
--- /dev/null
+++ b/layout/base/tests/test_bug1226904.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1226904">Mozilla Bug 1226904</a>
+<iframe src="bug1226904.html" id="iframe" height="1000" width="1000" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1226904 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+ var doc = iframe.contentDocument;
+ var container = doc.getElementById("container");
+ var separator = doc.getElementById("separator");
+ var transformed = doc.getElementById("transformed");
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y), expected_element,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(600, 200, separator, "Separator object should be at right side");
+ check(900, 200, container, "Check bounds of separator object");
+ check(200, 600, transformed, "Transformed object should be at left side");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1246622.html b/layout/base/tests/test_bug1246622.html
new file mode 100644
index 000000000..78d51b145
--- /dev/null
+++ b/layout/base/tests/test_bug1246622.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for Bug 1246622</title>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ #grandparent {
+ position: absolute;
+ height: 100px;
+ width: 100px;
+ left: 400px;
+ transform-style: preserve-3d;
+ }
+
+ #parent {
+ transform: translateX(0px);
+ }
+
+ #child {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ transform-style: preserve-3d;
+ }
+ </style>
+</head>
+<body>
+<div id="grandparent">
+ <div id="parent">
+ <div id="child"></div>
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1246622 **/
+
+is(document.elementFromPoint(450,50), document.getElementById("child"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1278021.html b/layout/base/tests/test_bug1278021.html
new file mode 100644
index 000000000..f17ec9cf6
--- /dev/null
+++ b/layout/base/tests/test_bug1278021.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for Bug 1278021</title>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style type="text/css">
+ #parent {
+ transform-style: preserve-3d;
+ }
+
+ #firstchild {
+ width: 100%;
+ height: 226px;
+ background-color: blue;
+ }
+
+ #secondchild {
+ display: block;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ }
+</style>
+</head>
+<body>
+<div id="parent">
+ <div id="firstchild"></div>
+ <div id="secondchild"></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1278021 **/
+
+is(document.elementFromPoint(50,50), document.getElementById("secondchild"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug332655-1.html b/layout/base/tests/test_bug332655-1.html
new file mode 100644
index 000000000..8110cbfd3
--- /dev/null
+++ b/layout/base/tests/test_bug332655-1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332655
+-->
+<head>
+ <title>Test for Bug 332655</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332655">Mozilla Bug 332655</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput"
+ style="-moz-appearance: none"> <!-- bug 1204897 workaround -->
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 332655 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ synthesizeKey("a", { });
+ synthesizeKey("b", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("\u05d0", { });
+ synthesizeKey("\u05d1", { });
+ s1 = snapshotWindow(window);
+
+ synthesizeKey(" ", { });
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "space after LTR + RTL shouldn't change direction: expected " +
+ str1 + " but got " + str2);
+
+ synthesizeKey("VK_BACK_SPACE", { });
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "backspace should restore the status quo: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug332655-2.html b/layout/base/tests/test_bug332655-2.html
new file mode 100644
index 000000000..b4dac3691
--- /dev/null
+++ b/layout/base/tests/test_bug332655-2.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332655
+-->
+<head>
+ <title>Test for Bug 332655</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332655">Mozilla Bug 332655</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput"
+ style="-moz-appearance: none"> <!-- bug 1234659 workaround -->
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 332655 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function repeatKey(key, repetitions) {
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey(key, {});
+ }
+}
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ synthesizeKey("\u05d0", { });
+ synthesizeKey("a", { });
+ synthesizeKey("b", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("\u05d1", { });
+ synthesizeKey("\u05d2", { });
+ s1 = snapshotWindow(window);
+
+ // 4 LEFT to get to the beginning of the line: HOME doesn't work on OS X
+ repeatKey("VK_LEFT", 4);
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("\u05d0", { });
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "deleting and inserting RTL char at beginning of line shouldn't change: expected " +
+ str1 + " but got " + str2);
+
+ textInput.select();
+ synthesizeKey("a", { });
+ synthesizeKey("b", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("\u05d1", { });
+ synthesizeKey("\u05d2", { });
+ // 4 LEFT to get to the beginning of the line: HOME doesn't work on OS X
+ repeatKey("VK_LEFT", 4);
+ synthesizeKey("\u05d0", { });
+
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "the order entering Bidi text shouldn't change rendering: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug369950.html b/layout/base/tests/test_bug369950.html
new file mode 100644
index 000000000..374996103
--- /dev/null
+++ b/layout/base/tests/test_bug369950.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=369950
+-->
+<head>
+ <title>Test for Bug 369950</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=369950">Mozilla Bug 369950</a>
+<p id="display">
+ <iframe id="i" src="bug369950-subframe.xml" width="200" height="100"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 369950 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Can't just run our code here, because we might not have painting
+ // unsuppressed yet. Do it async.
+ SimpleTest.executeSoon(doTheTest);
+});
+
+function doTheTest() {
+ // do a layout flush
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+
+ // And do the rest of it later
+ SimpleTest.executeSoon(reallyDoTheTest);
+}
+
+function reallyDoTheTest() {
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+
+ // We want coords relative to the iframe, so subtract off rect2.left/top.
+ // 7px is a guess to get us from the bottom of the iframe into the scrollbar
+ // groove for the horizontal scrollbar on the bottom.
+ synthesizeMouse($("i").contentDocument.documentElement,
+ -rect2.left + rect.width / 2, rect.height - rect2.top - 7,
+ {}, $("i").contentWindow);
+ // Scroll is async, so give it time
+ SimpleTest.executeSoon(checkScroll);
+};
+
+function checkScroll() {
+ // do a layout flush
+ var rect = $("i").getBoundingClientRect();
+ // And do the rest of it later
+ SimpleTest.executeSoon(reallyCheckScroll);
+}
+
+function reallyCheckScroll() {
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+ isnot($("i").contentWindow.scrollX, 0, "Clicking scrollbar should scroll");
+
+ // Not doing things below here, since avoiding the scroll arrows
+ // cross-platform is a huge pain.
+ SimpleTest.finish();
+ return;
+
+ // 8px horizontal offset is a guess to get us into the scr
+ synthesizeMouse($("i").contentDocument.documentElement, -rect2.left + 8,
+ rect.height - rect2.top - 7, {}, $("i").contentWindow);
+ // Scroll is async, so give it time
+ SimpleTest.executeSoon(finishUp);
+}
+
+function finishUp() {
+ is($("i").contentWindow.scrollX, 0, "Clicking scrollbar should scroll back");
+ SimpleTest.finish();
+};
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug370436.html b/layout/base/tests/test_bug370436.html
new file mode 100644
index 000000000..a3e8471bb
--- /dev/null
+++ b/layout/base/tests/test_bug370436.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=370436
+-->
+<head>
+ <title>Test for Bug 370436</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script class="testbody" type="application/javascript">
+<!--
+words = new Array()
+
+function expandStringOffsetToWord(data, offset)
+{
+ if (data == undefined) return "";
+
+ var m1 = data.substr(0, offset).match(/\w+$/) || "";
+ var m2 = data.substr(offset).match(/^\w+/) || "";
+ return m1 + m2;
+}
+
+function onContextMenu(e)
+{
+ var node = SpecialPowers.wrap(e).rangeParent;
+ var offset = e.rangeOffset;
+
+ var word = expandStringOffsetToWord(node.data, offset);
+ words.push(word);
+}
+
+function startTest()
+{
+ ta = document.getElementById('blah');
+ ta.focus();
+ ta.selectionStart = ta.selectionEnd = ta.value.length;
+
+// Note: This test, intentionally or by accident, relies on sending button '0'
+// with contextMenu, which triggers some key-equiv stuff in
+// PresShell::AdjustContextMenuKeyEvent.
+var mouseParams = { type: 'contextmenu', button: 0 };
+
+ /* Put cursor at start and middle of "sheep" */
+ synthesizeKey("VK_UP", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ /* Put cursor at the end of "hello" */
+ synthesizeKey("VK_UP", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("VK_RIGHT", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ /* Put cursor on "welcome" */
+ synthesizeKey("VK_UP", {})
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ is(words.pop(), "welcome", "Word 1 selected correctly");
+ is(words.pop(), "world" , "Word 2 selected correctly");
+ is(words.pop(), "hello" , "Word 3 selected correctly");
+ is(words.pop(), "hello" , "Word 4 selected correctly");
+ is(words.pop(), "sheep" , "Word 5 selected correctly");
+ is(words.pop(), "sheep" , "Word 6 selected correctly");
+ is(words.pop(), "sheep" , "Word 7 selected correctly");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish()
+SimpleTest.waitForFocus(startTest)
+-->
+</script>
+
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=370436">Mozilla Bug 370436</a></p>
+
+<textarea id="blah" rows="10" cols="80" oncontextmenu="onContextMenu(event); return false;">
+welcome
+hello world
+sheep
+</textarea>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug386575.xhtml b/layout/base/tests/test_bug386575.xhtml
new file mode 100644
index 000000000..d9a278eff
--- /dev/null
+++ b/layout/base/tests/test_bug386575.xhtml
@@ -0,0 +1,46 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386575
+-->
+<head>
+ <title>Test for Bug 386575</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386575">Mozilla Bug 386575</a>
+<p id="display">
+
+
+<!-- the image is http://dxr.mozilla.org/mozilla-central/source/layout/reftests/bugs/solidblue.png -->
+<!-- solidblue.png is a 16x16 image -->
+<table>
+ <tbody>
+ <div style="height:20px; min-height:100%">
+ <img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%20cHRM%00%00z%26%00%00%80%84%00%00%FA%00%00%00%80%E8%00%00u0%00%00%EA%60%00%00%3A%98%00%00%17p%9C%BAQ%3C%00%00%00%18tEXtSoftware%00Paint.NET%20v3.01%1C%AE%24E%00%00%00'IDAT8Ocd%10%AE%FF%CF%40%11%00%19%40%09%A6H3%D8%F5%94%D8%3Ej%00%24%F6F%03q4%0C%80%E9%00%00%D6%11y%92%15%8F%DC%CE%00%00%00%00IEND%AEB%60%82" style="height: 80%" />
+ </div>
+ </tbody>
+</table>
+
+
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 386575 **/
+
+
+SimpleTest.waitForExplicitFinish();
+ok(true,"This is an assertion test");
+SimpleTest.finish();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug388019.html b/layout/base/tests/test_bug388019.html
new file mode 100644
index 000000000..c695822aa
--- /dev/null
+++ b/layout/base/tests/test_bug388019.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=388019
+-->
+<head>
+ <title>Test for Bug 388019</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=388019">Mozilla Bug 388019</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 388019 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ var oldY = window.scrollY;
+ window.location.hash="#abc";
+ var newY = window.scrollY;
+ isnot(oldY, newY, "we scroll at all");
+ ok(oldY + 4000 < newY, "we scroll at least 4000 pixels");
+ window.scrollTo(0, 0); // make the results visible
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a name="abc"></a>You should see this text if you click on the link.
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug394057.html b/layout/base/tests/test_bug394057.html
new file mode 100644
index 000000000..f36d73904
--- /dev/null
+++ b/layout/base/tests/test_bug394057.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394057
+-->
+<head>
+ <title>Test for Bug 394057</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display { background: yellow; color: black; font-family: serif; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394057">Mozilla Bug 394057</a>
+<table id="display"><tr><td>MmMmMm...iiiIIIlll---</td></tr></table>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 394057 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var tableElement = document.getElementById("display");
+
+var CC = SpecialPowers.Cc;
+var CI = SpecialPowers.Ci;
+
+var fe =
+ CC["@mozilla.org/gfx/fontenumerator;1"].createInstance(CI.nsIFontEnumerator);
+var serifFonts = fe.EnumerateFonts("x-western", "serif", {});
+var monospaceFonts = fe.EnumerateFonts("x-western", "monospace", {});
+
+function table_width_for_font(font) {
+ tableElement.style.fontFamily = '"' + font + '"';
+ var result = tableElement.offsetWidth;
+ tableElement.style.fontFamily = "";
+ return result;
+}
+
+var serifIdx = 0;
+var monospaceIdx = 0;
+var monospaceWidth, serifWidth;
+monospaceWidth = table_width_for_font(monospaceFonts[monospaceIdx]);
+for (serifIdx in serifFonts) {
+ serifWidth = table_width_for_font(serifFonts[serifIdx]);
+ if (serifWidth != monospaceWidth)
+ break;
+}
+if (serifWidth == monospaceWidth) {
+ for (monospaceIdx in monospaceFonts) {
+ monospaceWidth = table_width_for_font(monospaceFonts[monospaceIdx]);
+ if (serifWidth != monospaceWidth)
+ break;
+ }
+}
+
+isnot(serifWidth, monospaceWidth,
+ "can't find serif and monospace fonts of different width");
+
+SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', serifFonts[serifIdx]]]}, step2);
+
+var serifWidthFromPref;
+function step2() {
+ serifWidthFromPref = tableElement.offsetWidth;
+ SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}, step3);
+}
+var monospaceWidthFromPref;
+function step3() {
+ monospaceWidthFromPref = tableElement.offsetWidth;
+
+ is(serifWidthFromPref, serifWidth,
+ "changing font pref should change width of table (serif)");
+ is(monospaceWidthFromPref, monospaceWidth,
+ "changing font pref should change width of table (monospace)");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug399284.html b/layout/base/tests/test_bug399284.html
new file mode 100644
index 000000000..09d729742
--- /dev/null
+++ b/layout/base/tests/test_bug399284.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=399284
+-->
+<head>
+ <title>Test for Bug 399284</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399284">Mozilla Bug 399284</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 399284 **/
+const testContent = "<p id='testPara'>The quick brown fox jumps over the lazy dog";
+
+var decoders = [
+ "Big5",
+ "Big5-HKSCS",
+ "EUC-JP",
+ "EUC-KR",
+ "gb18030",
+ "IBM866",
+ "ISO-2022-JP",
+ "ISO-8859-3",
+ "ISO-8859-4",
+ "ISO-8859-5",
+ "ISO-8859-6",
+ "ISO-8859-7",
+ "ISO-8859-8",
+ "ISO-8859-8-I",
+ "ISO-8859-10",
+ "ISO-8859-13",
+ "ISO-8859-14",
+ "ISO-8859-15",
+ "ISO-8859-16",
+ "ISO-8859-2",
+ "KOI8-R",
+ "KOI8-U",
+ "Shift_JIS",
+ "windows-1250",
+ "windows-1251",
+ "windows-1252",
+ "windows-1253",
+ "windows-1254",
+ "windows-1255",
+ "windows-1256",
+ "windows-1257",
+ "windows-1258",
+ "windows-874",
+ "x-mac-cyrillic",
+ "UTF-8",
+ "UTF-16LE",
+ "UTF-16BE"
+];
+
+var decoder;
+for (var i = 0; i < decoders.length; i++) {
+ var decoder = decoders[i];
+ var data;
+
+ // encode the content for non-ASCII compatible encodings
+ if (decoder == "UTF-16BE")
+ data = encodeUTF16BE(testContent);
+ else if (decoder == "UTF-16LE")
+ data = encodeUTF16LE(testContent);
+ else
+ data = encodeURI(testContent);
+ var dataURI = "data:text/html;charset=" + decoder + "," + data;
+
+ var testFrame = document.createElement("iframe");
+ frameID = decoder;
+ testFrame.setAttribute("id", frameID);
+ var testFrameObj = document.body.appendChild(testFrame);
+ testFrameObj.setAttribute("onload", "testFontSize('" + decoder + "')");
+ testFrameObj.contentDocument.location.assign(dataURI);
+}
+
+function encodeUTF16BE(string)
+{
+ var encodedString = "";
+ for (i = 0; i < string.length; ++i) {
+ encodedString += "%00";
+ encodedString += encodeURI(string.charAt(i));
+ }
+ return encodedString;
+}
+
+function encodeUTF16LE(string)
+{
+ var encodedString = "";
+ for (i = 0; i < string.length; ++i) {
+ encodedString += encodeURI(string.charAt(i));
+ encodedString += "%00";
+ }
+ return encodedString;
+}
+
+function testFontSize(frame)
+{
+ var iframeDoc = $(frame).contentDocument;
+ var size = parseInt(iframeDoc.defaultView.
+ getComputedStyle(iframeDoc.getElementById("testPara"),
+ null).
+ getPropertyValue("font-size"));
+ ok(size > 0, "font size assigned for " + frame);
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug399951.html b/layout/base/tests/test_bug399951.html
new file mode 100644
index 000000000..0d7ac83cc
--- /dev/null
+++ b/layout/base/tests/test_bug399951.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=399951
+-->
+<head>
+ <title>Test for Bug 399951</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body style="direction: rtl" onload="document.body.style.direction = 'ltr';">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399951">Mozilla Bug 399951</a>
+<div style="white-space: pre;">
+.i
+ h
+ f
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function test()
+{
+/** Test for Bug 399951 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug404209.xhtml b/layout/base/tests/test_bug404209.xhtml
new file mode 100644
index 000000000..104fdc69e
--- /dev/null
+++ b/layout/base/tests/test_bug404209.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404209
+-->
+<head>
+ <title>Test for Bug 404209</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div::first-letter { color: green; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404209">Mozilla Bug 404209</a>
+<table id="table" dir="rtl"><div><span id="v"><span><tfoot></tfoot>abcd</span></span></div></table>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function boom1()
+{
+ document.getElementById("table").style.borderRight = "1px solid magenta";
+ setTimeout(boom2, 400);
+}
+
+function boom2()
+{
+ var v = document.getElementById("v");
+ var newTD = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ newTD.setAttribute("width", "13%");
+ v.insertBefore(newTD, v.firstChild);
+ setTimeout(lastTest, 400);
+}
+
+function lastTest()
+{
+/** Test for Bug 404209 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(boom1, 400);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug416896.html b/layout/base/tests/test_bug416896.html
new file mode 100644
index 000000000..d77423275
--- /dev/null
+++ b/layout/base/tests/test_bug416896.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416896
+-->
+<head>
+ <title>Test for Bug 416896</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" type="text/css" id="l"
+ href="data:text/css,a { color: green }"/>
+ <style type="text/css" id="i"> a { color: blue; } </style>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416896">Mozilla Bug 416896</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 416896 **/
+ var inlineSheet = $("i").sheet;
+ isnot(inlineSheet, null, "Should have sheet here");
+
+ var linkedSheet = $("l").sheet;
+ isnot(linkedSheet, null, "Should have sheet here");
+
+ var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(SpecialPowers.Ci.inIDOMUtils);
+ const nsIDOMCSSStyleRule = SpecialPowers.Ci["nsIDOMCSSStyleRule"];
+ var inspectedRules = domUtils.getCSSStyleRules(document.links[0]);
+
+ var seenInline = false;
+ var seenLinked = false;
+
+ for (var i = 0; i < inspectedRules.Count(); ++i)
+ {
+ var rule =
+ SpecialPowers.unwrap(inspectedRules.GetElementAt(i).QueryInterface(nsIDOMCSSStyleRule));
+ var sheet = rule.parentStyleSheet;
+ if (sheet == inlineSheet) {
+ is(sheet.href, null, "It's an inline sheet");
+ is(seenInline, false, "Only one inline rule matches");
+ seenInline = true;
+ } else {
+ isnot(sheet.href, null, "Shouldn't have null href here");
+ if (sheet == linkedSheet) {
+ is(seenLinked, false, "Only one linked rule matches");
+ seenLinked = true;
+ }
+ }
+ }
+
+ is(seenLinked, true, "Didn't find the linked rule?");
+ is(seenInline, true, "Didn't find the inline rule?");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug423523.html b/layout/base/tests/test_bug423523.html
new file mode 100644
index 000000000..c1fedf9ae
--- /dev/null
+++ b/layout/base/tests/test_bug423523.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=423523
+-->
+<head>
+ <title>Test for Bug 423523</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body onload="setTimeout(runtests, 200)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=423523">Mozilla Bug 423523</a>
+<p id="display"></p>
+
+ <table>
+ <tbody><tr>
+ <td class="tdABB" id="tdTo">
+ <p id="par1">Some text...</p></td>
+ <td>
+ <div id="div1" style="border: 1px solid silver; width: 250px;" contenteditable="true">This is some editable text.</div>
+ </td></tr>
+ </tbody></table>
+
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 423523 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+
+ function divIsFocused() {
+ // Check if div is directly focused.
+ var divNode = document.getElementById("div1");
+ if (window.getSelection().focusNode == divNode) {
+ return true;
+ }
+ // Check if one of the div's children has focus.
+ var node = window.getSelection().focusNode;
+ var childNodes = divNode.childNodes;
+ for (var i=0; i<childNodes.length; i++) {
+ if (childNodes[i] == node) {
+ return true;
+ }
+ }
+ // Not focused (at least not the first gen kids, and
+ // that's ok for this test).
+ return false;
+ }
+
+ function selectionOffsetIs(expectedOffset) {
+ return window.getSelection().focusOffset == expectedOffset;
+ }
+
+ function sendMouseClick() {
+ var rect=document.getElementById('div1').getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left+1, rect.top+1, 0, 1, 0);
+ utils.sendMouseEvent('mouseup', rect.left+1, rect.top+1, 0, 1, 0);
+ }
+
+ function runtests() {
+ sendMouseClick();
+ window.getSelection().collapse(document.getElementById("div1").firstChild, 0);
+ ok(divIsFocused(), "Div should be focused [0].");
+
+ ok(divIsFocused(), "Div should be focused [1].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ synthesizeKey("VK_LEFT", { });
+ ok(divIsFocused(), "Div should be focused [2].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ synthesizeKey("VK_RIGHT", { });
+ ok(divIsFocused(), "Div should be focused [3].");
+ ok(selectionOffsetIs(1), "Caret should be at offset 1");
+
+ synthesizeKey("VK_LEFT", { });
+ ok(divIsFocused(), "Div should be focused [4].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ ok(divIsFocused(), "Div should be focused [5].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ sendMouseClick();
+
+ ok(divIsFocused(), "Div should be focused [6].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ synthesizeKey("VK_LEFT", { });
+
+ ok(divIsFocused(), "Div should be focused [7].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ SimpleTest.finish();
+ }
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug435293-interaction.html b/layout/base/tests/test_bug435293-interaction.html
new file mode 100644
index 000000000..d004d7d59
--- /dev/null
+++ b/layout/base/tests/test_bug435293-interaction.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ #test1 {
+ background: green;
+ height: 100px;
+ width: 100px;
+ -moz-transform: skew(30deg, 60deg) scale(2, 5) rotate(45deg) translate(2%, 50px);
+ -moz-transform-origin: 100% 50%;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<div id="content">
+ <div id="test1" onclick="testFinish();">
+ test
+ </div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runtests();
+
+function runtests() {
+ function doClick() {
+ sendMouseEvent({type: 'click'}, 'test1');
+ }
+ setTimeout(doClick, 300);
+}
+
+function testFinish(){
+ ok(1, "We can still interact with the item after it is transformed");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug435293-scale.html b/layout/base/tests/test_bug435293-scale.html
new file mode 100644
index 000000000..91777af3c
--- /dev/null
+++ b/layout/base/tests/test_bug435293-scale.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ .test {
+ background: green;
+ height: 100px;
+ width: 100px;
+ }
+ #test1 {
+ -moz-transform: scalex(0.5);
+ }
+ #test2 {
+ -moz-transform: scaley(0.5);
+ }
+ #test3 {
+ -moz-transform: scale(0.5, 0.5);
+ }
+ #test4 {
+ -moz-transform: scale(0.5, 0.5, 0.5);
+ }
+ #test5 {
+ -moz-transform: scale(80%, none);
+ }
+ #test6 {
+ -moz-transform: scale(640000, 0.0000000000000000001);
+ }
+ #test7 {
+ -moz-transform: scale(2em, 4px);
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<p id="display"></p>
+<div id="content">
+ <div id="test1" class="test">
+ test
+ </div>
+ <p id="test2" class="test">
+ test
+ </p>
+ <div id="test3" class="test">
+ test
+ </div>
+ <div id="test4" class="test">
+ test
+ </div>
+ <div id="test5" class="test">
+ test
+ </div>
+ <div id="test6" class="test">
+ test
+ </div>
+ <div id="test7" class="test">
+ test
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+runtests();
+
+function runtests() {
+ var style = window.getComputedStyle(document.getElementById("test1"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(0.5, 0, 0, 1, 0, 0)",
+ "Scalex proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test2"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, 0, 0, 0.5, 0, 0)",
+ "Scaley proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test3"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(0.5, 0, 0, 0.5, 0, 0)",
+ "Scale proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test4"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "Three dimensional scale should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test5"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "Percent values in scale should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test6"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(640000, 0, 0, 1e-19, 0, 0)",
+ "Ensure wacky values are accepted");
+
+ style = window.getComputedStyle(document.getElementById("test7"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "No unit values allowed in scale");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug435293-skew.html b/layout/base/tests/test_bug435293-skew.html
new file mode 100644
index 000000000..287fdc085
--- /dev/null
+++ b/layout/base/tests/test_bug435293-skew.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ /* Skewed boxes can get very big. The .pane wrapper prevents them
+ from obscuring the test results. */
+ .pane {
+ height: 300px;
+ width: 300px;
+ float: left;
+ overflow: auto;
+ border: 1px solid black;
+ }
+ .test {
+ background: green;
+ height: 100px;
+ width: 100px;
+ margin: 100px;
+ }
+
+ /* Radian units are not used in this test because our CSS
+ implementation stores all dimensional values in single-
+ precision floating point, which makes it impossible to
+ hit mathematically interesting angles with radians.
+ Degrees and grads do not suffer this problem. */
+ #test1 {
+ -moz-transform: skewx(30deg);
+ }
+ #test2 {
+ -moz-transform: skewy(60deg);
+ }
+ #test3 {
+ -moz-transform: skew(45deg, 45deg);
+ }
+ #test4 {
+ -moz-transform: skew(360deg, 45deg);
+ }
+ #test5 {
+ -moz-transform: skew(45deg, 150grad);
+ }
+ #test6 {
+ -moz-transform: skew(80%, 78px);
+ }
+ #test7 {
+ -moz-transform: skew(2em, 40ex);
+ }
+ #test8 {
+ -moz-transform: skew(-45deg, -465deg);
+ }
+ #test9 {
+ -moz-transform: skew(30deg, 30deg, 30deg);
+ }
+
+ /* approach the singularity from the negative side */
+ #test10 {
+ -moz-transform: skew(50grad, 90.001deg);
+ }
+ #test11 {
+ -moz-transform: skew(300grad, 90.001deg);
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<p id="display"></p>
+<div id="content">
+ <div class="pane"><div id="test1" class="test">test</div></div>
+ <div class="pane"><p id="test2" class="test">test</p></div>
+ <div class="pane"><div id="test3" class="test">test</div></div>
+ <div class="pane"><div id="test4" class="test">test</div></div>
+ <div class="pane"><div id="test5" class="test">test</div></div>
+ <div class="pane"><div id="test6" class="test">test</div></div>
+ <div class="pane"><div id="test7" class="test">test</div></div>
+ <div class="pane"><div id="test8" class="test">test</div></div>
+ <div class="pane"><div id="test9" class="test">test</div></div>
+ <div class="pane"><div id="test10" class="test">test</div></div>
+ <div class="pane"><div id="test11" class="test">test</div></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+runtests();
+
+function runtests() {
+ // For test 1 we need to handle the contingency that different systems may
+ // round differently. We will parse out the values and compare them
+ // individually. The matrix should be: matrix(1, 0, 0.57735, 1, 0, 0)
+ var style = window.getComputedStyle(document.getElementById("test1"), "");
+ var tformStyle = style.getPropertyValue("-moz-transform");
+ var tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test1: skewx: param 0 is 1");
+ is((+tformValues[1]), 0, "Test1: skewx: param 1 is 0");
+ ok(verifyRounded(tformValues[2], 0.57735), "Test1: skewx: Rounded param 2 is in bounds");
+ is((+tformValues[3]), 1, "Test1: skewx: param 3 is 1");
+ is((+tformValues[4]), 0, "Test1: skewx: param 4 is 0");
+ is((+tformValues[5]), 0, "Test1: skewx: param 5 is 0");
+
+ // Again, handle rounding for test 2, proper matrix should be:
+ // matrix(1, 1.73205, 0, 1, 0, 0)
+ style = window.getComputedStyle(document.getElementById("test2"), "");
+ tformStyle = style.getPropertyValue("-moz-transform");
+ tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test2: skewy: param 0 is 1");
+ ok(verifyRounded(tformValues[1], 1.73205), "Test2: skewy: Rounded param 1 is in bounds");
+ is((+tformValues[2]), 0, "Test2: skewy: param 2 is 0");
+ is((+tformValues[3]), 1, "Test2: skewy: param 3 is 1");
+ is((+tformValues[4]), 0, "Test2: skewy: param 4 is 0");
+ is((+tformValues[5]), 0, "Test2: skewy: param 5 is 0");
+
+ style = window.getComputedStyle(document.getElementById("test3"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, 1, 1, 1, 0, 0)",
+ "Test3: Skew proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test4"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, 1, 0, 1, 0, 0)",
+ "Test4: Skew angle wrap: proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test5"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, -1, 1, 1, 0, 0)",
+ "Test5: Skew mixing deg and grad");
+
+ style = window.getComputedStyle(document.getElementById("test6"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "Test6: Skew with invalid units");
+
+ style = window.getComputedStyle(document.getElementById("test7"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "Test7: Skew with more invalid units");
+
+ // Test 8: skew with negative degrees, here again we must handle rounding.
+ // The matrix should be: matrix(1, 3.73206, -1, 1, 0, 0)
+ style = window.getComputedStyle(document.getElementById("test8"), "");
+ tformStyle = style.getPropertyValue("-moz-transform");
+ tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test8: Test skew with negative degrees-param 0 is 1");
+ ok(verifyRounded(tformValues[1], 3.73206), "Test8: Rounded param 1 is in bounds");
+ is((+tformValues[2]), -1, "Test8: param 2 is -1");
+ is((+tformValues[3]), 1, "Test8: param 3 is 1");
+ is((+tformValues[4]), 0, "Test8: param 4 is 0");
+ is((+tformValues[5]), 0, "Test8: param 5 is 0");
+
+ style = window.getComputedStyle(document.getElementById("test9"), "");
+ is(style.getPropertyValue("-moz-transform"), "none",
+ "Test9: Skew in 3d should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test10"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, -10000, 1, 1, 0, 0)",
+ "Test10: Skew with nearly infinite numbers");
+
+ style = window.getComputedStyle(document.getElementById("test11"), "");
+ is(style.getPropertyValue("-moz-transform"), "matrix(1, -10000, 10000, 1, 0, 0)",
+ "Test11: Skew with more infinite numbers");
+}
+
+// Verifies that aVal is +/- 0.00001 of aTrueVal
+// Returns true if so, false if not
+function verifyRounded(aVal, aTrueVal) {
+ return (Math.abs(aVal - aTrueVal).toFixed(5) <= 0.00001);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug449781.html b/layout/base/tests/test_bug449781.html
new file mode 100644
index 000000000..ad32793b2
--- /dev/null
+++ b/layout/base/tests/test_bug449781.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449781
+-->
+<head>
+ <title>Test for Bug 449781</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=449781">Mozilla Bug 449781</a>
+<p id="display">Canary</p>
+<iframe src="about:blank" id="ourFrame" style="visibility: hidden">
+</iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var s1, s2, s3, s4;
+
+/** Test for Bug 449781 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ s1 = snapshotWindow(window);
+
+ $("ourFrame").style.display = "none";
+ is($("ourFrame").offsetWidth, 0, "Unexpected width after hiding");
+ $("ourFrame").style.display = "";
+ is($("ourFrame").clientWidth, 300, "Unexpected width after showing");
+
+ s2 = snapshotWindow(window);
+
+ var equal, str1, str2;
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "Show/hide should have no effect",
+ "got " + str1 + " but expected " + str2);
+
+ var viewer =
+ SpecialPowers.wrap($("ourFrame")).contentWindow
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .contentViewer;
+ viewer.fullZoom = 2;
+
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "Zoom should have no effect",
+ "got " + str1 + " but expected " + str2);
+
+ $("display").style.display = "none";
+
+ s4 = snapshotWindow(window);
+ [equal, str1, str2] = compareSnapshots(s3, s4, true);
+ ok(!equal, "Should be able to see the canary");
+
+ SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug450930.xhtml b/layout/base/tests/test_bug450930.xhtml
new file mode 100644
index 000000000..7162fc123
--- /dev/null
+++ b/layout/base/tests/test_bug450930.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450930
+-->
+<head>
+ <title>Test for Bug 450930 (MozAfterPaint)</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+/** Test for Bug 450930 **/
+SimpleTest.waitForExplicitFinish();
+var subwindow = window.open("./bug450930.xhtml", "bug450930", "width=800,height=1000");
+
+function finishTests() {
+ subwindow.close();
+ SimpleTest.finish();
+}
+
+]]></script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug465448.xul b/layout/base/tests/test_bug465448.xul
new file mode 100644
index 000000000..8a09158fa
--- /dev/null
+++ b/layout/base/tests/test_bug465448.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Bug 465448"
+ onload="loaded()"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script><![CDATA[
+SimpleTest.waitForExplicitFinish();
+var loadedCalled = false;
+var win = window.open("data:text/html,<body onload='window.opener.loaded()'><div style='height:200px; width:100px;'>", "_blank", "width=600,height=600");
+
+function loaded() {
+ if (!loadedCalled) {
+ loadedCalled = true;
+ return;
+ }
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ ok(win.innerWidth >= 100, "innerWidth");
+ ok(win.innerHeight >= 200, "innerHeight");
+ win.close();
+ SimpleTest.finish();
+}
+]]></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/layout/base/tests/test_bug469170.html b/layout/base/tests/test_bug469170.html
new file mode 100644
index 000000000..13f79d39c
--- /dev/null
+++ b/layout/base/tests/test_bug469170.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469170
+-->
+<head>
+ <title>Test for Bug 469170</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest();">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469170">Mozilla Bug 469170</a></p>
+
+<iframe id="source" width="50" height="50"
+ src="data:text/html,%3Chtml%3E%3C%2Fhtml%3E"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 469170 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var source = document.getElementById('source').contentWindow;
+ rect = { left: 0, top: 0,
+ width: source.innerWidth, height: source.innerHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var components = [ "red", "green", "blue", "alpha" ];
+
+ var data = context.getImageData(0, 0, canvas.width, canvas.height).data;
+ var failed = false;
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] != 0) {
+ is(data[i], 0, "pixel " + Math.floor(i/4) + " " + components[i%4]);
+ failed = true;
+ }
+ }
+ if (!failed) {
+ ok(!failed, "all pixels fully transparent");
+ }
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug471126.html b/layout/base/tests/test_bug471126.html
new file mode 100644
index 000000000..df7479e50
--- /dev/null
+++ b/layout/base/tests/test_bug471126.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471126
+-->
+<head>
+ <title>Test for Bug 471126</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471126">Mozilla Bug 471126</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 471126 **/
+
+function test()
+{
+ var selection = window.getSelection();
+ selection.collapse(document.documentElement, 0);
+ document.documentElement.addEventListener("click", function(){ var foo = window; }, false);
+}
+test();
+ok(true, "Shouldn't leak");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug499538-1.html b/layout/base/tests/test_bug499538-1.html
new file mode 100644
index 000000000..054ce726e
--- /dev/null
+++ b/layout/base/tests/test_bug499538-1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499538
+-->
+<head>
+ <title>Test for Bug 499538</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499538">Mozilla Bug 499538</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput" style="-moz-appearance:none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 499538 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ synthesizeKey("a", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("\u0639", { });
+ synthesizeKey("\u063A", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("b", { });
+ s1 = snapshotWindow(window);
+
+ textInput.select();
+ synthesizeKey("a", { });
+ synthesizeKey(" ", { });
+ synthesizeKey(" ", { });
+ synthesizeKey("b", { });
+ synthesizeKey("VK_LEFT", { });
+ synthesizeKey("VK_LEFT", { });
+ synthesizeKey("\u0639", { });
+ synthesizeKey("\u063A", { });
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "Arabic text between English words not connected: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug514127.html b/layout/base/tests/test_bug514127.html
new file mode 100644
index 000000000..a8ca40f59
--- /dev/null
+++ b/layout/base/tests/test_bug514127.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514127
+-->
+<head>
+ <title>Test for Bug 514127</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest();">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=514127">Mozilla Bug 514127</a></p>
+
+<!--
+iframe source is
+<html><body style='background: rgb(0,0,255); width: 100px; height: 50100px;'></body></html>
+-->
+<iframe id="source" width="50" height="50"
+ src="data:text/html,%3Chtml%3E%3Cbody%20style%3D%27background%3A%20rgb%280%2C0%2C255%29%3B%20width%3A%20100px%3B%20height%3A%2050100px%3B%27%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 514127 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+
+ var source = document.getElementById('source').contentWindow;
+ var canvasWidth = 50;
+ var canvasHeight = 50;
+
+ rect = { left: 25, top: 50000,
+ width: canvasWidth, height: canvasHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var data = context.getImageData(0, 0, canvasWidth, canvasHeight).data;
+ var failed = false;
+ for (var i = 0; i < data.length; i+=4) {
+ if (data[i] != 0 || data[i+1] != 0 || data[i+2] != 255 || data[i+3] != 255) {
+ failed = true;
+ break;
+ }
+ }
+ ok(!failed, "all pixels blue");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug518777.html b/layout/base/tests/test_bug518777.html
new file mode 100644
index 000000000..298f18d24
--- /dev/null
+++ b/layout/base/tests/test_bug518777.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=518777
+-->
+<head>
+ <title>Test for Bug 518777</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function dotest() {
+ var canvasWidth = 50;
+ var canvasHeight = 50;
+ var source = document.getElementById("source").contentWindow;
+ rect = { left: 25, top: 25,
+ width: canvasWidth, height: canvasHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var data = context.getImageData(0, 0, canvasWidth, canvasHeight).data;
+ var i;
+ for (i = 0; i < data.length; i += 4) {
+ if (data[i] != 0 || data[i + 1] != 0 || data[i + 2] != 255 || data[i + 3] != 255)
+ break;
+ }
+ ok(i >= data.length, "all pixels blue");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518777">Mozilla Bug 518777</a></p>
+
+<!--
+iframe source is
+<html><body onload='window.scrollTo(0,99999999); document.documentElement.offsetWidth; window.parent.dotest();' style='background: rgb(0,0,255); width: 100px; height: 50100px;'></body></html>
+-->
+<iframe id="source" width="50" height="50"
+ src="data:text/html,%3Chtml%3E%3Cbody%20onload%3D%27window.scrollTo%280%2C99999999%29%3B%20document.documentElement.offsetWidth%3B%20window.parent.dotest%28%29%3B%27%20style%3D%27background%3A%20rgb%280%2C0%2C255%29%3B%20width%3A%20100px%3B%20height%3A%2050100px%3B%27%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug548545.xhtml b/layout/base/tests/test_bug548545.xhtml
new file mode 100644
index 000000000..039a07de4
--- /dev/null
+++ b/layout/base/tests/test_bug548545.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548545
+-->
+<head>
+ <title>Test for Bug 548545</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #content { margin: 1em; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548545">Mozilla Bug 548545</a>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test for Bug 548545 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var content = document.getElementById("content");
+
+var CC = SpecialPowers.Cc;
+var CI = SpecialPowers.Ci;
+
+var fe =
+ CC["@mozilla.org/gfx/fontenumerator;1"].createInstance(CI.nsIFontEnumerator);
+var allFonts = fe.EnumerateFonts(null, null, {});
+
+var idx = 0;
+var list = "";
+for (idx in allFonts) {
+ list += allFonts[idx] + "<br/>";
+}
+content.innerHTML = list;
+
+ok(true,"Loaded the font list");
+SimpleTest.finish();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug558663.html b/layout/base/tests/test_bug558663.html
new file mode 100644
index 000000000..2e20deec3
--- /dev/null
+++ b/layout/base/tests/test_bug558663.html
@@ -0,0 +1,37 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Bug 558663 test</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ iframe {
+ width: 600px;
+ height: 400px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="container"></div>
+ </body>
+ <script>
+ if (navigator.platform.startsWith("Linux")) {
+ // For e10s issue of bug 966157
+ SimpleTest.expectAssertions(0, 2);
+ }
+ SimpleTest.waitForExplicitFinish();
+ // AccessibleCaret's pref is checked only when PresShell is initialized. To turn
+ // off the pref, we test bug 558663 in an iframe.
+ SpecialPowers.pushPrefEnv({"set": [['layout.accessiblecaret.enabled', false]]}, function() {
+ var iframe = document.createElement("iframe");
+ iframe.src = "bug558663.html";
+ document.getElementById('container').appendChild(iframe);
+ });
+ </script>
+</html>
diff --git a/layout/base/tests/test_bug559499.html b/layout/base/tests/test_bug559499.html
new file mode 100644
index 000000000..e7c81b282
--- /dev/null
+++ b/layout/base/tests/test_bug559499.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html style="background:yellow">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=559499
+-->
+<head>
+ <title>Test for Bug 559499</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="position:relative; z-index:-1; padding-top:100px;">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=559499">Mozilla Bug 514127</a></p>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 559499 **/
+
+is(document.elementFromPoint(50, 50), document.body, "Able to hit body");
+document.documentElement.style.display = "table";
+is(document.elementFromPoint(50, 50), document.body, "Able to hit body (table)");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug569520.html b/layout/base/tests/test_bug569520.html
new file mode 100644
index 000000000..cd6e5ad86
--- /dev/null
+++ b/layout/base/tests/test_bug569520.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569520
+-->
+<head>
+ <title>Test for Bug 569520</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=569520">Mozilla Bug 569520</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569520 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var start = window.performance.now();
+var firstListenerArg;
+var secondListenerArg;
+var thirdListenerTime;
+
+// callback arg is in the same timeline as performance.now()
+function thirdListener(t) {
+ thirdListenerTime = t;
+
+ ok(secondListenerArg >= firstListenerArg, // callback args from consecutive requestAnimationFrame
+ "Second listener should fire after first listener");
+
+ ok(thirdListenerTime >= secondListenerArg,
+ "Third listener should fire after second listener");
+
+ ok(firstListenerArg >= start, "First listener should fire after start");
+
+ SimpleTest.finish();
+}
+
+// callback arg is from requestAnimationFrame and comparable to performance.now()
+function secondListener(t) {
+ secondListenerArg = t;
+ requestAnimationFrame(thirdListener);
+}
+
+function firstListener(t) {
+ firstListenerArg = t;
+ requestAnimationFrame(secondListener);
+}
+
+addLoadEvent(function() {
+ setTimeout(function() {
+ requestAnimationFrame(firstListener);
+ }, 100);
+ });
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582181-1.html b/layout/base/tests/test_bug582181-1.html
new file mode 100644
index 000000000..c2cd399bd
--- /dev/null
+++ b/layout/base/tests/test_bug582181-1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582181
+-->
+<head>
+ <title>Test for Bug 582181</title>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582181">Mozilla Bug 582181</a>
+<p id="display"></p>
+<div id="content" dir="rtl">
+<textarea rows="4" style="resize: none" id="testInput">Ùارسی
+[[en:Farsi]]</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 582181 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ s1 = snapshotWindow(window);
+
+ textInput.focus();
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_RETURN", { });
+ textInput.blur();
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "enter after text shouldn't change rendering: expected " +
+ str1 + " but got " + str2);
+
+ textInput.focus();
+ synthesizeKey("VK_BACK_SPACE", { });
+ textInput.blur();
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "backspace shouldn't change rendering: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582181-2.html b/layout/base/tests/test_bug582181-2.html
new file mode 100644
index 000000000..9577991ae
--- /dev/null
+++ b/layout/base/tests/test_bug582181-2.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582181
+-->
+<head>
+ <title>Test for Bug 582181</title>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582181">Mozilla Bug 582181</a>
+<p id="display"></p>
+<div id="content" dir="rtl">
+<textarea rows="5" id="testInput" style="resize:none">Blah blah
+Ùلان Ùلان
+&lt;ref&gt;ooo&lt;/ref&gt;
+&lt;references /&gt;</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 582181 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ s1 = snapshotWindow(window);
+
+ textInput.focus();
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ textInput.blur();
+ s2 = snapshotWindow(window);
+
+ [unequal, str1, str2] = compareSnapshots(s1, s2, false);
+ ok(unequal, "backspace after text should change rendering: got " + str2);
+
+ textInput.focus();
+ synthesizeKey(">", { });
+ textInput.blur();
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "entering '>' should restore original rendering: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582771.html b/layout/base/tests/test_bug582771.html
new file mode 100644
index 000000000..17781643c
--- /dev/null
+++ b/layout/base/tests/test_bug582771.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582771
+-->
+<head>
+ <title>Test for Bug 582771</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .test {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ -moz-user-select: none;
+ }
+ </style>
+</head>
+<body onload="setTimeout('runTest()', 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582771">Mozilla Bug 582771</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 582771 **/
+
+SimpleTest.waitForExplicitFinish();
+var d1;
+var d2;
+var d1mousemovecount = 0;
+var d2mousemovecount = 0;
+
+function sendMouseMove(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousemove', rect.left + 5, rect.top + 5, 0, 0, 0);
+}
+
+function sendMouseDown(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function sendMouseUp(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function log(s) {
+ document.getElementById("l").textContent += s + "\n";
+}
+
+function d2Listener(e) {
+ log(e.type + ", " + e.target.id);
+ is(e.target, d2, "d2 should have got mousemove.");
+ ++d2mousemovecount;
+}
+
+function d1Listener(e) {
+ log(e.type + ", " + e.target.id);
+ d1.setCapture(true);
+}
+
+function d1Listener2(e) {
+ log(e.type + ", " + e.target.id);
+ d2.setCapture(true);
+}
+
+function d1MouseMoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ ++d1mousemovecount;
+}
+
+function runTest() {
+ d1 = document.getElementById("d1");
+ d2 = document.getElementById("d2");
+ d2.addEventListener("mousemove", d2Listener, true);
+ document.body.offsetLeft;
+ sendMouseMove(d2);
+ is(d2mousemovecount, 1, "Should have got mousemove");
+
+ // This shouldn't enable capturing, since we're not in a right kind of
+ // event listener.
+ d1.setCapture(true);
+ sendMouseDown(d1);
+ sendMouseMove(d2);
+ sendMouseUp(d1);
+ is(d2mousemovecount, 2, "Should have got mousemove");
+
+ d1.addEventListener("mousedown", d1Listener, true);
+ d1.addEventListener("mousemove", d1MouseMoveListener, true);
+ sendMouseDown(d1);
+ sendMouseMove(d2);
+ is(d2mousemovecount, 2, "Shouldn't have got mousemove");
+ is(d1mousemovecount, 1, "Should have got mousemove");
+ sendMouseUp(d1);
+ d1.removeEventListener("mousedown", d1Listener, true);
+ d1.removeEventListener("mousemove", d1MouseMoveListener, true);
+
+ // Nothing should be capturing the event.
+ sendMouseMove(d2);
+ is(d2mousemovecount, 3, "Should have got mousemove");
+
+
+ d1.addEventListener("mousemove", d1Listener2, true);
+ sendMouseDown(d1);
+ sendMouseMove(d1); // This should call setCapture to d2!
+ d1.removeEventListener("mousemove", d1Listener2, true);
+ d1.addEventListener("mousemove", d1MouseMoveListener, true);
+ sendMouseMove(d1); // This should send mouse event to d2.
+ is(d1mousemovecount, 1, "Shouldn't have got mousemove");
+ is(d2mousemovecount, 4, "Should have got mousemove");
+ sendMouseUp(d1);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<div class="test" id="d1">&nbsp;</div><br><div class="test" id="d2">&nbsp;</div>
+<pre id="l"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug583889.html b/layout/base/tests/test_bug583889.html
new file mode 100644
index 000000000..163c0f1db
--- /dev/null
+++ b/layout/base/tests/test_bug583889.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583889
+-->
+<head>
+ <title>Test for Bug 583889</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583889">Mozilla Bug 583889</a>
+<iframe id="inner" style="width: 10px; height: 10px;"></iframe>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+/** Test for Bug 583889 **/
+SimpleTest.waitForExplicitFinish();
+
+function grabEventAndGo(event) {
+ gen.send(event);
+}
+
+function runTest() {
+ window.onload = grabEventAndGo;
+ // Wait for onLoad event.
+ yield;
+
+ var inner = $("inner");
+ inner.src = "bug583889_inner1.html";
+ window.onmessage = grabEventAndGo;
+ // Wait for message from 'inner' iframe.
+ event = yield;
+
+ while (event.data != "done") {
+ data = JSON.parse(event.data);
+ is(data.top, 300, "should remain at same top");
+ is(data.left, 300, "should remain at same left");
+
+ // Wait for message from 'inner' iframe.
+ event = yield;
+ }
+
+ // finish(), yet let the test actually end first, to be safe.
+ SimpleTest.executeSoon(SimpleTest.finish);
+ // "End" generator.
+ yield;
+}
+
+var gen = runTest();
+gen.next();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug588174.html b/layout/base/tests/test_bug588174.html
new file mode 100644
index 000000000..cd6e5ad86
--- /dev/null
+++ b/layout/base/tests/test_bug588174.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569520
+-->
+<head>
+ <title>Test for Bug 569520</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=569520">Mozilla Bug 569520</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569520 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var start = window.performance.now();
+var firstListenerArg;
+var secondListenerArg;
+var thirdListenerTime;
+
+// callback arg is in the same timeline as performance.now()
+function thirdListener(t) {
+ thirdListenerTime = t;
+
+ ok(secondListenerArg >= firstListenerArg, // callback args from consecutive requestAnimationFrame
+ "Second listener should fire after first listener");
+
+ ok(thirdListenerTime >= secondListenerArg,
+ "Third listener should fire after second listener");
+
+ ok(firstListenerArg >= start, "First listener should fire after start");
+
+ SimpleTest.finish();
+}
+
+// callback arg is from requestAnimationFrame and comparable to performance.now()
+function secondListener(t) {
+ secondListenerArg = t;
+ requestAnimationFrame(thirdListener);
+}
+
+function firstListener(t) {
+ firstListenerArg = t;
+ requestAnimationFrame(secondListener);
+}
+
+addLoadEvent(function() {
+ setTimeout(function() {
+ requestAnimationFrame(firstListener);
+ }, 100);
+ });
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug603550.html b/layout/base/tests/test_bug603550.html
new file mode 100644
index 000000000..0df24f28b
--- /dev/null
+++ b/layout/base/tests/test_bug603550.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=603550
+-->
+<head>
+ <title>Test for Bug 603550</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .test {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ -moz-user-select: none;
+ }
+ </style>
+</head>
+<body onload="setTimeout('runTest()', 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=603550">Mozilla Bug 603550</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 603550 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function sendMouseMoveFaraway(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousemove', rect.left + 5000, rect.top + 5000, 0, 0, 0);
+}
+
+function sendMouseDown(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function sendMouseUp(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function fireEvent(target, event) {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.dispatchDOMEventViaPresShell(target, event, true);
+}
+
+function fireDrop(element) {
+ var ds = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"].
+ getService(SpecialPowers.Ci.nsIDragService);
+
+ ds.startDragSession();
+
+ var event = document.createEvent("DragEvent");
+ event.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+ fireEvent(element, event);
+
+ event = document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+ fireEvent(element, event);
+
+ ds.endDragSession(false);
+ ok(!ds.getCurrentSession(), "There shouldn't be a drag session anymore!");
+}
+
+function runTest() {
+ var d1 = document.getElementById("d1");
+ var didGetMouseMove = false;
+ sendMouseDown(d1);
+ document.addEventListener("mousemove",
+ function (e) {
+ didGetMouseMove = (e.target == document);
+ },
+ true);
+ sendMouseMoveFaraway(d1);
+ ok(didGetMouseMove, "Should have got mousemove!");
+ sendMouseUp(d1);
+
+ didGetMouseMove = false;
+ document.addEventListener("mousedown",
+ function (e) {
+ e.preventDefault();
+ },
+ true);
+ sendMouseDown(d1);
+ sendMouseMoveFaraway(d1);
+ ok(didGetMouseMove, "Should have got mousemove! (2)");
+ sendMouseUp(d1);
+
+ didGetMouseMove = false;
+ sendMouseDown(d1);
+ fireDrop(d1);
+ sendMouseMoveFaraway(d1);
+ ok(!didGetMouseMove, "Shouldn't have got mousemove!");
+
+
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+<div class="test" id="d1">&nbsp;</div>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug607529.html b/layout/base/tests/test_bug607529.html
new file mode 100644
index 000000000..332996084
--- /dev/null
+++ b/layout/base/tests/test_bug607529.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607529
+-->
+<head>
+ <title>Test for Bug 607529</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=607529">Mozilla Bug 607529</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ /* General idea: Open a new window (needed because we don't bfcache
+ subframes) that uses requestAnimationFrame, navigate it, navigate it
+ back, and verify that the animations are still running. */
+
+ var doneOneLoad = false;
+
+ /** Test for Bug 607529 **/
+ var done = false;
+ window.onmessage = function(e) {
+ isnot(e.data, "notcached", "Should never end up not being cached");
+
+ if (e.data == "loaded" && !doneOneLoad) {
+ doneOneLoad = true;
+ w.location = "data:text/html,<script>window.onload = function() { opener.postMessage('goback', '*'); }</" + "script>";
+ }
+ else if (e.data == "goback") {
+ w.history.back();
+ }
+ else if (e.data == "revived") {
+ w.postMessage("report", "*");
+ }
+ else if (e.data == "callbackHappened") {
+ // We might get this message more than once, if the other page queues up
+ // more than one callbackHappened message before we manage to close it.
+ // Protect against calling SimpleTest.finish() more than once.
+ if (!done) {
+ w.close();
+ window.onmessage = null;
+ SimpleTest.finish();
+ done = true;
+ }
+ } else {
+ var msg = JSON.parse(e.data);
+ if (msg.error) {
+ window.onerror(msg.msg, msg.url, msg.line);
+ }
+ }
+ };
+
+ var w = window.open("file_bug607529.html");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug629838.html b/layout/base/tests/test_bug629838.html
new file mode 100644
index 000000000..34f12fc16
--- /dev/null
+++ b/layout/base/tests/test_bug629838.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for MozAfterPaint</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <script type="application/javascript">
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+<embed type="application/x-test" width="100" height="100" id="p"
+ drawmode="solid" color="FF00FF00"></embed>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var initialPaintCount, afterPaintCount;
+var color = 0;
+
+function onAfterPaint () {
+ afterPaintCount += 1;
+}
+
+function startTest() {
+ setTimeout(function () {
+ afterPaintCount = 0;
+ initialPaintCount = window.mozPaintCount;
+ window.addEventListener("MozAfterPaint", onAfterPaint, true);
+ doBackgroundFlicker();
+ }, 500);
+}
+
+document.addEventListener("DOMContentLoaded", startTest, true);
+
+// Unfortunately we cannot reliably assert that mozPaintCount and afterPaintCount increment perfectly
+// in sync, because they can diverge in the presence of OS-triggered paints or system load.
+// Instead, wait for a minimum number of afterPaint events to at least ensure that they are being fired.
+const minimumAfterPaintsToPass = 10;
+
+function doPluginFlicker() {
+ ok(true, "Plugin color iteration " + color +
+ ", afterpaint count: " + afterPaintCount +
+ ", mozpaint count: " + window.mozPaintCount);
+ if (afterPaintCount >= minimumAfterPaintsToPass) {
+ ok(true, "afterPaintCount incremented enough from plugin color changes.");
+ SimpleTest.finish();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ var str = color.toString(16);
+ if (str.length < 2) {
+ str = "0" + str;
+ }
+ str = "FF" + str + str + str;
+ document.getElementById("p").setColor(str);
+ setTimeout(doPluginFlicker, 0);
+}
+
+function doBackgroundFlicker() {
+ ok(true, "Background color iteration " + color +
+ ", afterpaint count: " + afterPaintCount +
+ ", mozpaint count: " + window.mozPaintCount);
+ if (afterPaintCount >= minimumAfterPaintsToPass) {
+ ok(true, "afterPaintCount incremented enough from background color changes.");
+ afterPaintCount = 0;
+ initialPaintCount = window.mozPaintCount;
+ doPluginFlicker();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.body.style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doBackgroundFlicker, 0);
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug644768.html b/layout/base/tests/test_bug644768.html
new file mode 100644
index 000000000..297cd1c3b
--- /dev/null
+++ b/layout/base/tests/test_bug644768.html
@@ -0,0 +1,62 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=644768
+ -->
+ <head>
+ <title>Test for Bug 644768</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body onload="test()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=644768">Mozilla Bug 644768</a>
+ <p id="display"></p>
+ <div id="content">
+ <!-- test text is
+ == زادروزها ==
+ * [[Û±Û³Û°Û·]]
+ -->
+ <textarea id="testInput" dir="rtl" cols="80" rows="25" style="-moz-appearance:none">
+
+== &#x0632;&#x0627;&#x062F;&#x0631;&#x0648;&#x0632;&#x0647;&#x0627; ==
+* [[&#x06F1;&#x06F3;&#x06F0;&#x06F7;]]</textarea>
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ /** Test for Bug 644768 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ var textInput = $("testInput");
+ var s1, s2, equal, str1, str2;
+
+ textInput.focus();
+ s1 = snapshotWindow(window);
+
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_DELETE", { });
+ synthesizeKey("VK_RETURN", { });
+ // Bug 1016184: Touch caret will hide due to key event.
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "newline before bidi text shouldn't change direction: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+ }
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug646757.html b/layout/base/tests/test_bug646757.html
new file mode 100644
index 000000000..f4a1ffcf2
--- /dev/null
+++ b/layout/base/tests/test_bug646757.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646757
+-->
+<head>
+ <title>Test for Bug 646757</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body style="margin:0" id="body">
+<div style="height:20.3px; width:400px; background:pink" id="d1"></div>
+<div style="height:20px; width:400px; background:yellow" id="d2"></div>
+<div style="height:9.7px; width:400px;" id="space1"></div>
+<div style="height:20.7px; width:400px; background:pink" id="d3"></div>
+<div style="height:20px; width:400px; background:yellow" id="d4"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646757">Mozilla Bug 646757</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function testPoint(x, y, id) {
+ is(document.elementFromPoint(x, y).id, id,
+ "checking element at " + x + "," + y);
+}
+
+/** Test for Bug 646757 **/
+testPoint(200, 20, "d1");
+testPoint(200, 20.2, "d1");
+testPoint(200, 20.4, "d2");
+testPoint(200, 21, "d2");
+
+testPoint(200, 70, "d3");
+testPoint(200, 70.6, "d3");
+testPoint(200, 70.8, "d4");
+testPoint(200, 71, "d4");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug66619.html b/layout/base/tests/test_bug66619.html
new file mode 100644
index 000000000..663556c0e
--- /dev/null
+++ b/layout/base/tests/test_bug66619.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=66619
+-->
+<head>
+ <title>Test for Bug 66619</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=66619">Mozilla Bug 66619</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 66619 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ is(window.scrollY, 0, "window should initially be at top");
+
+ document.getElementById("first").focus();
+ var first = window.scrollY;
+ isnot(first, 0, "we scrolled to first anchor");
+ ok(first + window.innerHeight > 4000,
+ "we scrolled enough to show the anchor");
+
+ window.scrollTo(0, 0);
+ document.getElementById("second").focus();
+ var second = window.scrollY;
+
+ window.scrollTo(0, 0);
+ document.getElementById("third").focus();
+ var third = window.scrollY;
+
+ is(second, first, "we scrolled the second line of the anchor into view");
+ isnot(third, second, "we scrolled the second line of the anchor into view");
+ ok(third > second, "we scrolled the second line of the anchor into view");
+
+ window.scrollTo(0, 0); // make the results visible
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug667512.html b/layout/base/tests/test_bug667512.html
new file mode 100644
index 000000000..600b6d0f6
--- /dev/null
+++ b/layout/base/tests/test_bug667512.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667512
+-->
+<head>
+ <title>Test for Bug 667512</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<table contenteditable="true"><tbody><tr><td id="b"><br id="a"></td></tr></tbody></table>
+<span style="display: list-item;direction: rtl;"></span>
+<script type="application/javascript">
+
+/** Test for Bug 667512 **/
+function appendElements() {
+ window.focus();
+ window.getSelection().collapse(document.documentElement, 0);
+
+ var x=document.getElementById('a');
+ x.parentNode.removeChild(x);
+
+ var x=document.getElementById('b');
+ x.parentNode.removeChild(x);
+
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RIGHT", {});
+
+ ok(true, "Should not crash!");
+ SimpleTest.finish();
+}
+
+addLoadEvent(appendElements);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/tests/test_bug677878.html b/layout/base/tests/test_bug677878.html
new file mode 100644
index 000000000..cb9e05a26
--- /dev/null
+++ b/layout/base/tests/test_bug677878.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677878
+-->
+<head>
+ <title>Test for Bug 677878</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ #test1 {
+ background: green;
+ height: 100px;
+ width: 100px;
+ -moz-transform: scale(20, 20);
+ -moz-transform-origin: 0 0%;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677878">Mozilla Bug 677878</a>
+<div id="content">
+ <div id="test1">
+ <div id="test2" onclick="testFinish();">
+ test
+ </div>
+ </div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runtests();
+
+function runtests() {
+ function doClick() {
+ document.getElementById("test2").addEventListener("mousedown", testFinish, true);
+ // Don't target the center because the center could actually be outside the
+ // viewport.
+ synthesizeMouse(document.getElementById("test2"), 10, 10, { type: "mousedown" })
+ }
+ setTimeout(doClick, 300);
+}
+
+function testFinish(event){
+ ok(true, "We can still interact with the item after it is transformed");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug687297.html b/layout/base/tests/test_bug687297.html
new file mode 100644
index 000000000..6ec9aaf4f
--- /dev/null
+++ b/layout/base/tests/test_bug687297.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=687297
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 687297</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=687297">Mozilla Bug 687297</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 687297 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var size_a=0, size_b=0, size_c=0;
+
+ window.report_size_a = function(s) {
+ size_a = s;
+ };
+
+ window.report_size_b = function(s) {
+ size_b = s;
+ };
+
+ window.report_size_c = function(s) {
+ size_c = s;
+
+ isnot(size_a, size_b, "Font sizes are changing with global language-specific minimum font size");
+ is(size_c, size_a, "Font sizes are equal, propagating only the presentation-level base minimum font size");
+
+ SimpleTest.finish();
+ };
+
+ SpecialPowers.pushPrefEnv(
+ {'set':[["font.minimum-size.ja", 120]]},
+ function() {
+ window.open("bug687297_a.html", '_blank');
+ }
+ );
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug696020.html b/layout/base/tests/test_bug696020.html
new file mode 100644
index 000000000..41bd2d8b0
--- /dev/null
+++ b/layout/base/tests/test_bug696020.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696020
+-->
+<head>
+ <title>Test for Bug 696020</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696020">Mozilla Bug 696020</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 696020 **/
+
+
+function startTest() {
+ var testFrame = document.getElementById("tf").contentWindow;
+ testFrame.focus();
+ var didHandleKeyEvent = false;
+ testFrame.addEventListener("keypress",
+ function(e) {
+ is(e.target, testFrame.document.body,
+ "Body element should be event target for key events!");
+ didHandleKeyEvent = true;
+ });
+ synthesizeKey("A", {}, testFrame);
+ ok(didHandleKeyEvent, "Should have handled a key event!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(startTest)
+
+
+</script>
+</pre>
+<iframe id="tf"></iframe>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug718809.html b/layout/base/tests/test_bug718809.html
new file mode 100644
index 000000000..33075347d
--- /dev/null
+++ b/layout/base/tests/test_bug718809.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=718809
+-->
+<head>
+ <title>Test for Bug 718809</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+<body style=margin:0>
+<div style="background:blue;height:50px;width:100px; -moz-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 10, 1); -moz-transform-origin:0 0"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=718809">Mozilla Bug 718809</a>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var rect = document.querySelector("div").getBoundingClientRect();
+
+ is(rect.top, 0, "Incorrect bounding rect");
+ is(rect.left, 0, "Incorrect bounding rect");
+ is(rect.right, 100, "Incorrect bounding rect");
+ is(rect.bottom, 50, "Incorrect bounding rect");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug725426.html b/layout/base/tests/test_bug725426.html
new file mode 100644
index 000000000..f02030362
--- /dev/null
+++ b/layout/base/tests/test_bug725426.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725426
+-->
+<title>Test for bug 725426</title>
+<script src=/tests/SimpleTest/SimpleTest.js></script>
+<link rel=stylesheet href=/tests/SimpleTest/test.css>
+<body style=margin:0>
+<div style="-moz-transform: perspective(200px)">
+<div style="-moz-transform: translatez(-100px);
+width:100px;height:100px;background:blue">
+</div></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725426">
+Mozilla Bug 725426</a>
+<pre id=test>
+<script class=testbody>
+var rect = document.querySelector("div>div").getBoundingClientRect();
+is(rect.top, 0, "Incorrect bounding rect top");
+is(rect.right, 100, "Incorrect bounding rect top");
+is(rect.bottom, 100, "Incorrect bounding rect top");
+is(rect.left, 0, "Incorrect bounding rect top");
+</script>
+</pre>
diff --git a/layout/base/tests/test_bug731777.html b/layout/base/tests/test_bug731777.html
new file mode 100644
index 000000000..190a28dfb
--- /dev/null
+++ b/layout/base/tests/test_bug731777.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 731777</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ position: relative;
+ height: 300px;
+ width: 300px;
+ margin: 50px 100px;
+ border: 2px solid blue;
+ background-color: #044B0A;
+
+ -moz-perspective: 500px;
+ overflow:hidden;
+ }
+
+ #inner {
+ margin: 0px;
+ width: 480px;
+ border: 2px solid blue;
+ height: 220px;
+ background-color: #844BCA;
+
+ -moz-transform: rotateY(91deg) translateX(0px) translateZ(0px);
+ transition: 5s;
+ }
+
+ </style>
+</head>
+<body>
+<div id="container">
+ <div id="inner"></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 731777 **/
+
+is(document.elementFromPoint(325,170), document.getElementById("inner"), "Able to hit transformed object");
+is(document.elementFromPoint(405,170), document.getElementById("inner"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug749186.html b/layout/base/tests/test_bug749186.html
new file mode 100644
index 000000000..94e11f365
--- /dev/null
+++ b/layout/base/tests/test_bug749186.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=749186
+Note that this is a crashtest, but because of the special privileges
+required, it needs to be run as a mochitest. Thus, the expected
+behavior of this test is that it actually loads and doesn't crash the
+browser.
+-->
+ <head>
+ <title>Test for Bug 749186 (Crashtest)</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ function endTest() {
+ ok(true, 'test finished without crashing');
+ SimpleTest.finish();
+ }
+
+ function removeBoldStyle() {
+ document.getElementById('b').removeAttribute('style');
+ SpecialPowers.pushPrefEnv({'set': [['font.size.inflation.emPerLine', 0]]},endTest);
+ }
+
+ function startTest() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ SpecialPowers.pushPrefEnv({'set': [['font.size.inflation.emPerLine', 8]]},removeBoldStyle);
+ }
+
+ startTest();
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=749186">Bug 749186</a>
+ <iframe id="a" style="display: none;"></iframe>
+ <div id="b" style="display: inline;"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug761572.html b/layout/base/tests/test_bug761572.html
new file mode 100644
index 000000000..02a3cfb66
--- /dev/null
+++ b/layout/base/tests/test_bug761572.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=761572
+-->
+<head>
+ <title>Test for Bug 761572</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=761572">Mozilla Bug 761572</a>
+<div id="content">
+ <div id="d" style="background:lime; width:50px; height:50px" onmouseup="doUp()" onclick="doClick()"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var d = document.getElementById("d");
+
+function doUp() {
+ d.style.display = "none";
+}
+function doClick() {
+ ok(true, "Check click received");
+ SimpleTest.finish();
+}
+
+function doTest() {
+ // synthesizes a mousedown/mouseup pair
+ synthesizeMouse(d, 10, 10, {});
+}
+
+SimpleTest.waitForFocus(doTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug770106.html b/layout/base/tests/test_bug770106.html
new file mode 100644
index 000000000..ff440f738
--- /dev/null
+++ b/layout/base/tests/test_bug770106.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 770106</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<span id="s">Hello</span>
+<button><div style="pointer-events:none; position:relative; width:100px; background:yellow; left:-100px;">Kitty</div></button>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 770106 **/
+
+var sRect = s.getBoundingClientRect();
+is(document.elementFromPoint(sRect.left + sRect.width/2, sRect.top + sRect.height/2),
+ document.getElementById("s"), "Correct object selected");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug842853-2.html b/layout/base/tests/test_bug842853-2.html
new file mode 100644
index 000000000..2b777abfc
--- /dev/null
+++ b/layout/base/tests/test_bug842853-2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=842853
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 842853</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 842853 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function verifyAfterLoad() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ is(win.scrollY,500);
+ SimpleTest.finish();
+ return;
+ }
+}
+
+function runTest() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,500);
+ e.setAttribute("onload","verifyAfterLoad()");
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=842853">Mozilla Bug 842853</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<iframe src="file_bug842853.html"></iframe>
+<script>
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug842853.html b/layout/base/tests/test_bug842853.html
new file mode 100644
index 000000000..2f9a1d11d
--- /dev/null
+++ b/layout/base/tests/test_bug842853.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=842853
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 842853</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 842853 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ is(win.scrollY,500);
+ SimpleTest.finish();
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,500);
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=842853">Mozilla Bug 842853</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+var e = document.createElement('iframe');
+var url = 'data:text/html,<a href="%23anchor">Click to scroll to anchor</a><div style="height:5000px"></div><a name="anchor">FAIL</a>'
+e.setAttribute('src',url);
+e.setAttribute('onload','runTest()');
+document.body.appendChild(e);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug849219.html b/layout/base/tests/test_bug849219.html
new file mode 100644
index 000000000..666f418b4
--- /dev/null
+++ b/layout/base/tests/test_bug849219.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=849219
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 849219</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 849219 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ is(win.scrollY,0);
+ SimpleTest.finish();
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,0);
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=849219">Mozilla Bug 849219</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+var e = document.createElement('iframe');
+var url = 'data:text/html,<a href="%23anchor">Click to scroll to anchor</a><div style="height:5000px"></div><a name="anchor">FAIL</a>'
+e.setAttribute('src',url);
+e.setAttribute('onload','runTest()');
+document.body.appendChild(e);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug851445.html b/layout/base/tests/test_bug851445.html
new file mode 100644
index 000000000..5dd79dfae
--- /dev/null
+++ b/layout/base/tests/test_bug851445.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=851445
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 851445</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=851445">Mozilla Bug 851445</a>
+<p id="display"></p>
+<iframe id="f" style="width:400px; height:400px;"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function handleLoad() {
+ f.contentWindow.scrollTo(0,100);
+ function handleLoad2() {
+ // Verify that the scroll position was retained
+ is(f.contentWindow.scrollY, 100);
+ SimpleTest.finish();
+ }
+ f.onload = handleLoad2;
+ f.contentWindow.location.reload();
+}
+
+f.src = "bug851445_helper.html?" + Math.random();
+f.onload = handleLoad;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug851485.html b/layout/base/tests/test_bug851485.html
new file mode 100644
index 000000000..8be136b84
--- /dev/null
+++ b/layout/base/tests/test_bug851485.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=851485
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 851485</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 851485 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+function delayedVerifyScroll(win) {
+ ok(win.scrollY > 3000);
+ SimpleTest.finish();
+}
+
+function verifyScroll(event) {
+ var win = event.target.defaultView;
+ win.onscroll = "";
+ setTimeout(function(){delayedVerifyScroll(win)},500)
+}
+
+function clickLink(link,win) {
+ win.document.body.offsetHeight;
+ synthesizeMouseAtCenter(link, {type: "mousedown"}, win);
+ synthesizeMouseAtCenter(link, {type: "mouseup"}, win);
+ sendMouseEvent({type: "click"}, link, win);
+}
+
+function verifyAfterLoad() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ is(win.scrollY,500);
+ var link = win.document.getElementsByTagName('a')[0];
+ win.onscroll = verifyScroll;
+ clickLink(link,win);
+ return;
+ }
+}
+
+function runTest() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,500);
+ e.setAttribute("onload","verifyAfterLoad()");
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=851485">Mozilla Bug 851485</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<iframe src="file_bug842853.html"></iframe>
+<script>
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug858459.html b/layout/base/tests/test_bug858459.html
new file mode 100644
index 000000000..2f594aa78
--- /dev/null
+++ b/layout/base/tests/test_bug858459.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=858459
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 858459</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 858459 **/
+
+var result = "";
+var timeout = null;
+var clicks = 0;
+const EXPECTED_RESULT = "change select";
+
+function logEvent(ev,msg) {
+ result += ev.type + ' ' + msg;
+ ++clicks;
+ if (result.length > EXPECTED_RESULT.length)
+ finishTest();
+}
+
+document.onclick = function(event) { logEvent(event,"document"); }
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+function finishTest() {
+ if (!timeout) return;
+ clearTimeout(timeout);
+ timeout = null;
+ is(result,EXPECTED_RESULT,"");
+ SimpleTest.finish();
+}
+
+function runTest() {
+ // Need a timeout to check that an event has _not_ occurred.
+ timeout = setTimeout(finishTest, 5000);
+ synthesizeMouseAtCenter(document.getElementById('test858459'), { });
+}
+
+ </script>
+</head>
+<body onload="SimpleTest.waitForFocus(runTest)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858459">Mozilla Bug 858459</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test"><div><select id="test858459" size=4 onclick="logEvent(event,'select');" onchange="logEvent(event,'select');var div = document.querySelector('#test div'); div.innerHTML='<p>'+div.innerHTML; document.body.offsetHeight;"><option>1111111111111111<option>2<option>3</select></div>
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-1.html b/layout/base/tests/test_bug93077-1.html
new file mode 100644
index 000000000..16fad6b2a
--- /dev/null
+++ b/layout/base/tests/test_bug93077-1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-2.html b/layout/base/tests/test_bug93077-2.html
new file mode 100644
index 000000000..24e17b735
--- /dev/null
+++ b/layout/base/tests/test_bug93077-2.html
@@ -0,0 +1,32 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-3.html b/layout/base/tests/test_bug93077-3.html
new file mode 100644
index 000000000..0c37be986
--- /dev/null
+++ b/layout/base/tests/test_bug93077-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p id=top>Top</p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#top"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-4.html b/layout/base/tests/test_bug93077-4.html
new file mode 100644
index 000000000..ea624086a
--- /dev/null
+++ b/layout/base/tests/test_bug93077-4.html
@@ -0,0 +1,35 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p><a name=top>Top</a></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#top"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-5.html b/layout/base/tests/test_bug93077-5.html
new file mode 100644
index 000000000..ffe9233cf
--- /dev/null
+++ b/layout/base/tests/test_bug93077-5.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p id=TOP>Top</p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#TOP"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-6.html b/layout/base/tests/test_bug93077-6.html
new file mode 100644
index 000000000..08cdb6c7f
--- /dev/null
+++ b/layout/base/tests/test_bug93077-6.html
@@ -0,0 +1,35 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p><a name=TOP>Top</a></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#TOP"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug968148.html b/layout/base/tests/test_bug968148.html
new file mode 100644
index 000000000..3cab8b073
--- /dev/null
+++ b/layout/base/tests/test_bug968148.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=968148
+-->
+<head>
+ <title>Test for Bug 968148</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript;version=1.7">
+ function setRemoteFrame() {
+ var iframe = document.getElementById("testFrame");
+ iframe.src = "bug968148_inner.html";
+
+ function messageListener(event) {
+ eval(event.data);
+ }
+
+ window.addEventListener("message", messageListener, false);
+ }
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, setRemoteFrame);
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <iframe id="testFrame" height="500" width="500"></iframe>
+</body>
+
diff --git a/layout/base/tests/test_bug970964.html b/layout/base/tests/test_bug970964.html
new file mode 100644
index 000000000..461911598
--- /dev/null
+++ b/layout/base/tests/test_bug970964.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970964
+-->
+<head>
+ <title>Test for Bug 970964</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript;version=1.7">
+ function setRemoteFrame() {
+ var iframe = document.getElementById("testFrame");
+ iframe.src = "bug970964_inner.html";
+
+ function messageListener(event) {
+ eval(event.data);
+ }
+
+ window.addEventListener("message", messageListener, false);
+ }
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, setRemoteFrame);
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <iframe id="testFrame" height="500" width="500"></iframe>
+</body>
+
diff --git a/layout/base/tests/test_bug976963.html b/layout/base/tests/test_bug976963.html
new file mode 100644
index 000000000..4b8da3a6e
--- /dev/null
+++ b/layout/base/tests/test_bug976963.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=976963
+-->
+ <head>
+ <meta charset="utf-8">
+ <meta name="author" content="Maksim Lebedev" />
+ <title>Test for Bug 976963</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true],
+ ["layout.reflow.synthMouseMove", false]
+ ]
+ }, startTest);
+ }
+ function startTest() {
+ var iframe = document.getElementById("testFrame");
+ iframe.src = "bug976963_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug977003.html b/layout/base/tests/test_bug977003.html
new file mode 100644
index 000000000..f6bca6975
--- /dev/null
+++ b/layout/base/tests/test_bug977003.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var number = 0;
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnPointerEvents(finishTest);
+ }
+ function turnOnPointerEvents(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.enabled", true]
+ ]
+ }, callback);
+ }
+ function finishTest() {
+ // Try to run several tests named as bug977003_inner_<number>.html
+ if(++number < 7)
+ iframe.src = "bug977003_inner_" + number + ".html";
+ else
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug990340.html b/layout/base/tests/test_bug990340.html
new file mode 100644
index 000000000..8d9984fbe
--- /dev/null
+++ b/layout/base/tests/test_bug990340.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990340
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 990340</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 990340 **/
+
+ function testbug990340() {
+ ok(document.querySelector('#bug990340 span').clientHeight < 100,
+ "'height' is in transition")
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=990340">Mozilla Bug 990340</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<style type="text/css">
+
+#bug990340::before {
+ content: ":before";
+}
+
+#bug990340 span {
+ display: inline-block;
+ border:1px solid blue;
+ height: 20px;
+ transition: height 30s;
+}
+
+#bug990340.s span {
+ height: 100px;
+}
+</style>
+<div id="bug990340" style="overflow:scroll">
+ <span>Transition height</span>
+</div>
+</pre>
+
+<script>
+document.body.offsetHeight;
+document.querySelector('#bug990340').className='s';
+testbug990340()
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/test_bug993936.html b/layout/base/tests/test_bug993936.html
new file mode 100644
index 000000000..9d62831ea
--- /dev/null
+++ b/layout/base/tests/test_bug993936.html
@@ -0,0 +1,161 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=993936
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 993936</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 993936 **/
+
+var currentId = 0;
+var evictedTouchesCount = 0;
+
+function testtouch(aOptions) {
+ if (!aOptions)
+ aOptions = {};
+ this.identifier = aOptions.identifier || 0;
+ this.target = aOptions.target || 0;
+ this.page = aOptions.page || {x: 0, y: 0};
+ this.radius = aOptions.radius || {x: 0, y: 0};
+ this.rotationAngle = aOptions.rotationAngle || 0;
+ this.force = aOptions.force || 1;
+}
+
+function touchEvent(aOptions) {
+ if (!aOptions) {
+ aOptions = {};
+ }
+ this.ctrlKey = aOptions.ctrlKey || false;
+ this.altKey = aOptions.altKey || false;
+ this.shiftKey = aOptions.shiftKey || false;
+ this.metaKey = aOptions.metaKey || false;
+ this.touches = aOptions.touches || [];
+ this.targetTouches = aOptions.targetTouches || [];
+ this.changedTouches = aOptions.changedTouches || [];
+}
+
+function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
+ var ids = [], xs=[], ys=[], rxs = [], rys = [],
+ rotations = [], forces = [];
+
+ for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
+ for (var i = 0; i < aEvent[touchType].length; i++) {
+ if (ids.indexOf(aEvent[touchType][i].identifier) == -1) {
+ ids.push(aEvent[touchType][i].identifier);
+ xs.push(aEvent[touchType][i].page.x);
+ ys.push(aEvent[touchType][i].page.y);
+ rxs.push(aEvent[touchType][i].radius.x);
+ rys.push(aEvent[touchType][i].radius.y);
+ rotations.push(aEvent[touchType][i].rotationAngle);
+ forces.push(aEvent[touchType][i].force);
+ }
+ }
+ }
+ return windowUtils.sendTouchEvent(aType,
+ ids, xs, ys, rxs, rys,
+ rotations, forces,
+ ids.length, aModifiers, 0);
+}
+
+function getSingleTouchEventForTarget(target, cwu) {
+ currentId++;
+ var bcr = target.getBoundingClientRect();
+ var touch = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: currentId,
+ });
+ var event = new touchEvent({
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch]
+ });
+ return event;
+}
+
+function getMultiTouchEventForTarget(target, cwu) {
+ currentId++;
+ var bcr = target.getBoundingClientRect();
+ var touch1 = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: currentId,
+ });
+ currentId++;
+ var touch2 = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width),
+ y: Math.round(bcr.top + bcr.height)},
+ target: target,
+ identifier: currentId,
+ });
+ var event = new touchEvent({
+ touches: [touch1, touch2],
+ targetTouches: [touch1, touch2],
+ changedTouches: [touch1, touch2]
+ });
+ return event;
+}
+
+function runTests() {
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+
+ var event1 = getMultiTouchEventForTarget(d0, cwu);
+ sendTouchEvent(cwu, "touchstart", event1, 0);
+ sendTouchEvent(cwu, "touchmove", event1, 0);
+ is(evictedTouchesCount, 0, "Still no evicted touches");
+
+ var event2 = getSingleTouchEventForTarget(d0, cwu);
+ sendTouchEvent(cwu, "touchstart", event2, 0);
+
+ // By now we should get touchend event
+ ok(evictedTouchesCount > 0, "Got evicted touch");
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=993936">Mozilla Bug 993936</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<div id="d0">
+ Test div
+</div>
+
+<script>
+var d0 = document.getElementById("d0");
+
+d0.addEventListener("touchend", function(ev) {
+ evictedTouchesCount++;
+});
+
+window.onload = function () {
+ setTimeout(function() {
+ runTests();
+ }, 0);
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_emulateMedium.html b/layout/base/tests/test_emulateMedium.html
new file mode 100644
index 000000000..fc3b4a16e
--- /dev/null
+++ b/layout/base/tests/test_emulateMedium.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=819930
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 819930</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ @media braille {
+ body {
+ background-color: rgb(255, 255, 0);
+ }
+ }
+
+ @media embossed {
+ body {
+ background-color: rgb(210, 180, 140);
+ }
+ }
+
+ @media handheld {
+ body {
+ background-color: rgb(0, 255, 0);
+ }
+ }
+
+ @media print {
+ body {
+ background-color: rgb(0, 255, 255);
+ }
+ }
+
+ @media projection {
+ body {
+ background-color: rgb(30, 144, 255);
+ }
+ }
+
+ @media screen {
+ body {
+ background-color: green;
+ }
+ }
+
+ @media speech {
+ body {
+ background-color: rgb(192, 192, 192);
+ }
+ }
+
+ @media tty {
+ body {
+ background-color: rgb(255, 192, 203);
+ }
+ }
+
+ @media tv {
+ body {
+ background-color: rgb(75, 0, 130);
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=819930">Mozilla Bug 819930</a>
+ <p id="display"></p>
+
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <script type="application/javascript;version=1.7">
+ let tests = [{name: 'braille', value: 'rgb(255, 255, 0)'},
+ {name: 'embossed', value: 'rgb(210, 180, 140)'},
+ {name: 'handheld', value: 'rgb(0, 255, 0)'},
+ {name: 'print', value: 'rgb(0, 255, 255)'},
+ {name: 'projection', value: 'rgb(30, 144, 255)'},
+ {name: 'speech', value: 'rgb(192, 192, 192)'},
+ {name: 'tty', value: 'rgb(255, 192, 203)'},
+ {name: 'tv', value: 'rgb(75, 0, 130)'}];
+
+ let originalColor = 'rgb(0, 128, 0)';
+ let body = document.body;
+
+ let getColor = function() {
+ return window.getComputedStyle(body)
+ .getPropertyValue('background-color');
+ };
+
+ tests.forEach(function(test) {
+ // Emulate the given media
+ SpecialPowers.emulateMedium(window, test.name);
+ is(getColor(), test.value, 'emulating ' + test.name + ' produced ' +
+ 'correct rendering');
+
+ // Do the @media screen rules get applied after ending the emulation?
+ SpecialPowers.stopEmulatingMedium(window);
+ is(getColor(), originalColor, 'Ending ' + test.name +
+ ' emulation restores style for original medium');
+
+ // CSS media types are case-insensitive; we should be too.
+ SpecialPowers.emulateMedium(window, test.name.toUpperCase());
+ is(getColor(), test.value,
+ test.name + ' emulation is case-insensitive');
+ SpecialPowers.stopEmulatingMedium(window);
+ });
+
+ // Emulating screen should produce the same rendering as when there is
+ // no emulation in effect
+ SpecialPowers.emulateMedium(window, 'screen');
+ is(getColor(), originalColor,
+ 'Emulating screen produces original rendering');
+ SpecialPowers.stopEmulatingMedium(window);
+
+ // Screen should be case-insensitive too
+ SpecialPowers.emulateMedium(window, 'SCREEN');
+ is(getColor(), originalColor, 'screen emulation is case-insensitive');
+ SpecialPowers.stopEmulatingMedium(window);
+
+ // An invalid parameter shouldn't fail. Given the CSS rules above,
+ // an invalid parameter should result in a different rendering from any
+ // produced thus far
+ try {
+ SpecialPowers.emulateMedium(window, 'clay');
+ let invalid = getColor();
+ tests.push({name: 'screen', value: 'green'});
+ tests.forEach(function(test) {
+ isnot(invalid, test.value, 'Emulating invalid type differs from ' +
+ test.name);
+ });
+ } catch (e) {
+ ok(false, 'Supplying invalid type to emulateMedium shouldn\'t throw');
+ }
+
+ SpecialPowers.stopEmulatingMedium(window);
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/layout/base/tests/test_event_target_iframe_oop.html b/layout/base/tests/test_event_target_iframe_oop.html
new file mode 100644
index 000000000..89aa19817
--- /dev/null
+++ b/layout/base/tests/test_event_target_iframe_oop.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921928
+-->
+<head>
+ <title>Test for bug 921928</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #dialer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 50px;
+ background: green;
+ }
+
+ #apps {
+ position: absolute;
+ left: 0;
+ top: 51px;
+ width: 100%;
+ height: 100px;
+ background: blue;
+ }
+
+ .hit {
+ position: absolute;
+ width: 3px;
+ height: 3px;
+ z-index: 20;
+ background: red;
+ border: 1px solid red;
+ }
+ </style>
+</head>
+<body id="body" style="margin:0; width:100%; height:100%">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var prefs = [
+ ["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 12],
+ ["ui.mouse.radius.topmm", 8],
+ ["ui.mouse.radius.rightmm", 4],
+ ["ui.mouse.radius.bottommm", 4],
+ ["ui.mouse.radius.visitedweight", 50],
+ ["dom.mozBrowserFramesEnabled", true]
+];
+
+var eventTarget;
+var debugHit = [];
+
+function endTest() {
+ SimpleTest.finish();
+ SpecialPowers.removePermission("browser", location.href);
+ for (var pref in prefs) {
+ SpecialPowers.pushPrefEnv({"clear": pref[0]}, function() {});
+ }
+}
+
+function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
+ eventTarget = null;
+ synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
+ try {
+ is(eventTarget.id, idTarget,
+ "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+ } catch (ex) {
+ ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
+ }
+}
+
+function showDebug() {
+ for (var i = 0; i < debugHit.length; i++) {
+ document.body.appendChild(debugHit[i]);
+ }
+
+ var screenshot = SpecialPowers.snapshotWindow(window, true);
+ dump('IMAGE:' + screenshot.toDataURL() + '\n');
+}
+
+/*
+ Setup the test environment: enabling event fluffing (all ui.* preferences),
+ and enabling remote process.
+*/
+function setupTest(cont) {
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({"set": prefs}, cont);
+}
+
+function execTest() {
+ /*
+ Creating two iframes that mimics the attention screen behavior on the
+ device:
+ - the 'dialer' iframe is the attention screen you have when a call is
+ in place. it is a green bar, so we copy it as green here too
+ - the 'apps' iframe mimics another application that is being run, be it
+ dialer, sms, ..., anything that the user might want to trigger during
+ a call
+
+ The bug we intent to reproduce here is that in this case, if the user taps
+ onto the top of the 'apps', the event fluffing code will in fact redirect
+ the event to the 'dialer' iframe. In practice, this is bug 921928 where
+ during a call the user wants to place a second call, and while typing the
+ phone number, wants to tap onto the 'delete' key to erase a digit, but ends
+ tapping and activating the dialer.
+ */
+ var dialer = document.createElement('iframe');
+ dialer.id = 'dialer';
+ dialer.src = '';
+ // Force OOP
+ dialer.setAttribute('mozbrowser', 'true');
+ dialer.setAttribute('remote', 'true');
+ document.body.appendChild(dialer);
+
+ var apps = document.createElement('iframe');
+ apps.id = 'apps';
+ apps.src = 'bug921928_event_target_iframe_apps_oop.html';
+ // Force OOP
+ apps.setAttribute('mozbrowser', 'true');
+ apps.setAttribute('remote', 'true');
+ document.body.appendChild(apps);
+
+ var handleEvent = function(event) {
+ eventTarget = event.target;
+
+ // We draw a small red div to show where the event has tapped
+ var hit = document.createElement('div');
+ hit.style.left = (event.clientX - 1.5) + 'px';
+ hit.style.top = (event.clientY - 1.5) + 'px';
+ hit.classList.add('hit');
+ debugHit.push(hit);
+ };
+
+ // In real life, the 'dialer' has a 'mousedown', so we mimic one too,
+ // to reproduce the same behavior
+ dialer.addEventListener('mousedown', function(e) {});
+
+ // This event listener is just here to record what iframe has been hit,
+ // and sets the 'eventTarget' to the iframe's id value so that the
+ // testMouseClick() code can correctly check. We cannot add it on the
+ // 'apps' otherwise it will alter the behavior of the test.
+ document.addEventListener('mousedown', handleEvent);
+
+ // In the following, the coordinates are relative to the iframe
+
+ // First, we check that tapping onto the 'dialer' correctly triggers the
+ // dialer.
+ testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with mouse input");
+ testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with touch input", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+
+ // Now this is it: we tap inside 'apps', but very close to the border between
+ // 'apps' and 'dialer'. Without the fix from this bug, this test will fail.
+ testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for mouse input");
+ testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for touch input", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+
+ // Show small red spots of where the click happened
+ // showDebug();
+
+ endTest();
+}
+
+function runTest() {
+ setupTest(execTest);
+}
+
+addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_event_target_radius.html b/layout/base/tests/test_event_target_radius.html
new file mode 100644
index 000000000..0657e9a69
--- /dev/null
+++ b/layout/base/tests/test_event_target_radius.html
@@ -0,0 +1,293 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780847
+-->
+<head>
+ <title>Test radii for mouse events</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .target { position:absolute; left:100px; top:100px; width:100px; height:100px; background:blue; }
+ </style>
+</head>
+<body id="body" onload="setTimeout(startTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
+<p id="display"></p>
+<div id="content">
+ <div id="ruler" style="position:absolute; left:0; top:0; width:1mozmm; height:0;"></div>
+
+ <!-- the iframe holding this test is only 300px tall on B2G, so we need to
+ make the t target shorter than normal to test the bottom edge fluffing
+ -->
+ <div class="target" style="height:80px" id="t" onmousedown="x=1"></div>
+
+ <div class="target" id="t2" hidden></div>
+
+ <input class="target" id="t3_1" hidden></input>
+ <a href="#" class="target" id="t3_2" hidden></a>
+ <label class="target" id="t3_3" hidden></label>
+ <button class="target" id="t3_4" hidden></button>
+ <select class="target" id="t3_5" hidden></select>
+ <textarea class="target" id="t3_6" hidden></textarea>
+ <div role="button" class="target" id="t3_7" hidden></div>
+ <div role="key" class="target" id="t3_8" hidden></div>
+ <img class="target" id="t3_9" hidden></img>
+
+ <div class="target" style="transform:translate(-80px,0);" id="t4" onmousedown="x=1" hidden></div>
+
+ <div class="target" style="left:0; z-index:1" id="t5_left" onmousedown="x=1" hidden></div>
+ <div class="target" style="left:106px;" id="t5_right" onmousedown="x=1" hidden></div>
+ <div class="target" style="left:0; top:210px;" id="t5_below" onmousedown="x=1" hidden></div>
+
+ <div class="target" id="t6" onmousedown="x=1" style="width: 300px" hidden>
+ <div id="t6_inner" style="position:absolute; left:-40px; top:20px; width:60px; height:60px; background:yellow;"></div>
+ <div id="t6_inner_clickable" style="position:absolute; left:-40px; top: 80px; width: 60px; height: 5px; background:red" onmousedown="x=1"></div>
+ </div>
+ <div id="t6_outer" style="position:absolute; left:360px; top:120px; width:60px; height:60px; background:green;" onmousedown="x=1" hidden></div>
+
+ <div class="target" id="t7" onmousedown="x=1" hidden></div>
+ <div class="target" id="t7_over" hidden></div>
+
+ <div id="t8" contenteditable="true" class="target" hidden></div>
+
+ <div id="t9" class="target" ontouchend="x=1" hidden></div>
+
+ <div id="t10_left" class="target" style="left:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_right" class="target" style="left:auto;right:-50px" onmousedown="x=1" hidden></div>
+ <div id="t10_top" class="target" style="top:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_bottom" class="target" style="top:auto;bottom:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_over" style="position:absolute; left:0; top:0; width:100%; height:100%; background:yellow;" hidden></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 12],
+ ["ui.mouse.radius.topmm", 8],
+ ["ui.mouse.radius.rightmm", 4],
+ ["ui.mouse.radius.bottommm", 4],
+ ["ui.mouse.radius.visitedweight", 50]]}, runTest);
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+function endTest() {
+ SimpleTest.finish();
+}
+
+var eventTarget;
+window.onmousedown = function(event) { eventTarget = event.target; };
+
+function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
+ eventTarget = null;
+ synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
+ try {
+ is(eventTarget.id, idTarget,
+ "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+ } catch (ex) {
+ ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
+ }
+}
+
+function setShowing(id, show) {
+ var e = document.getElementById(id);
+ e.hidden = !show;
+}
+
+var mm;
+function runTest() {
+ let resolution = 1;
+ if (SpecialPowers.Services.appinfo.name == "B2G") {
+ // This test runs on B2G as well, zoomed out. Therefore we need to account
+ // for the resolution as well, because the fluff area is relative to screen
+ // pixels rather than CSS pixels.
+ let out = {};
+ SpecialPowers.getDOMWindowUtils(window.top).getResolution(out);
+ resolution = 1.0 / out.value;
+ }
+ mm = document.getElementById("ruler").getBoundingClientRect().width * resolution;
+ ok(4*mm >= 10, "WARNING: mm " + mm + " too small in this configuration. Test results will be bogus");
+
+ // Test basic functionality: clicks sufficiently close to the element
+ // should be allowed to hit the element. We test points just inside and
+ // just outside the edges we set up in the prefs.
+ testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality");
+ testMouseClick("t", 100 + 11*mm, 10, "t", "basic functionality");
+ testMouseClick("t", 10, 80 + 9*mm, "body", "basic functionality");
+ testMouseClick("t", 10, 80 + 7*mm, "t", "basic functionality");
+ testMouseClick("t", -5*mm, 10, "body", "basic functionality");
+ testMouseClick("t", -3*mm, 10, "t", "basic functionality");
+ testMouseClick("t", 10, -5*mm, "body", "basic functionality");
+ testMouseClick("t", 10, -3*mm, "t", "basic functionality");
+
+ // When inputSource.touchOnly is true, mouse input is not retargeted.
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", true]]}, test2);
+}
+
+function test2() {
+ testMouseClick("t", 100 + 11*mm, 10, "body", "disabled for mouse input");
+ testMouseClick("t", 100 + 11*mm, 10, "t", "enabled for touch input", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+ testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality for touch", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", false]]}, test3);
+}
+
+function test3() {
+ setShowing("t", false);
+
+ // Now test the criteria we use to determine which elements are hittable
+ // this way.
+
+ setShowing("t2", true);
+ var t2 = document.getElementById("t2");
+ // Unadorned DIVs are not click radius targets
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "unadorned DIV");
+ // DIVs with the right event handlers are click radius targets
+ t2.onmousedown = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmousedown");
+ t2.onmousedown = null;
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onmousedown removed");
+ t2.onmouseup = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmouseup");
+ t2.onmouseup = null;
+ t2.onclick = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onclick");
+ t2.onclick = null;
+ // Keypresses don't make click radius targets
+ t2.onkeypress = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onkeypress");
+ t2.onkeypress = null;
+ setShowing("t2", false);
+
+ // Now check that certain elements are click radius targets and others are not
+ for (var i = 1; i <= 9; ++i) {
+ var id = "t3_" + i;
+ var shouldHit = i <= 8;
+ setShowing(id, true);
+ testMouseClick(id, 100 + 11*mm, 10, shouldHit ? id : "body",
+ "<" + document.getElementById(id).tagName + "> element");
+ setShowing(id, false);
+ }
+
+ // Check that our targeting computations take into account the effects of
+ // CSS transforms
+ setShowing("t4", true);
+ testMouseClick("t4", -1, 10, "t4", "translated DIV");
+ setShowing("t4", false);
+
+ // Test the prioritization of multiple targets based on distance to
+ // the target.
+ setShowing("t5_left", true);
+ setShowing("t5_right", true);
+ setShowing("t5_below", true);
+ testMouseClick("t5_left", 102, 10, "t5_left", "closest DIV is left");
+ testMouseClick("t5_left", 102.5, 10, "t5_left",
+ "closest DIV to midpoint is left because of its higher z-index");
+ testMouseClick("t5_left", 104, 10, "t5_right", "closest DIV is right");
+ testMouseClick("t5_left", 10, 104, "t5_left", "closest DIV is left");
+ testMouseClick("t5_left", 10, 105, "t5_left",
+ "closest DIV to midpoint is left because of its higher z-index");
+ testMouseClick("t5_left", 10, 106, "t5_below", "closest DIV is below");
+ setShowing("t5_left", false);
+ setShowing("t5_right", false);
+ setShowing("t5_below", false);
+
+ // Test behavior of nested elements.
+ // The following behaviors are questionable and may need to be changed.
+ setShowing("t6", true);
+ setShowing("t6_outer", true);
+ testMouseClick("t6_inner", -1, 10, "t6_inner",
+ "inner element is clickable because its parent is, even when it sticks outside parent");
+ testMouseClick("t6_inner", 39, -1, "t6_inner",
+ "when outside both inner and parent, but in range of both, the inner is selected");
+ testMouseClick("t6_inner", 45, -1, "t6_inner",
+ "clicking in clickable parent close to inner activates inner, not parent");
+ testMouseClick("t6_inner_clickable", 1, -1, "t6_inner",
+ "clicking on inner doesn't get redirected to inner_clickable because they are both clickable");
+ testMouseClick("t6_inner_clickable", 1, 1, "t6_inner_clickable",
+ "clicking on inner_clickable doesn't get redirected to inner because they are both clickable");
+ testMouseClick("t6_inner_clickable", 45, -1, "t6_inner",
+ "clicking on inner while backed by its parent still doesn't get redirected to inner_clickable");
+ testMouseClick("t6_inner_clickable", 45, 1, "t6_inner_clickable",
+ "clicking on inner_clickable while backed by its parent still doesn't get redirected to inner");
+ testMouseClick("t6_inner_clickable", 45, 6, "t6_inner_clickable",
+ "clicking on parent near inner_clickable gets redirected to inner_clickable rather than inner because it is closer");
+ // 280 is the distance from t6_inner's right edge to t6's right edge
+ // 240 is the distance from t6_inner's right edge to t6_outer's right edge.
+ // we want to click on t6, but at least 13mm away from t6_inner, so that
+ // t6_inner doesn't steal the click.
+ ok(13*mm < 280, "no point inside t6 that's not within radius of t6_inner; adjust layout of t6/inner/outer as needed");
+ testMouseClick("t6_outer", -240 + 13*mm, -1, "t6",
+ "clicking in clickable container close to outer activates parent, not outer");
+ testMouseClick("t6_outer", 1, 1, "t6_outer",
+ "clicking directly on the outer activates it");
+ setShowing("t6", false);
+ setShowing("t6_outer", false);
+
+ setShowing("t7", true);
+ setShowing("t7_over", true);
+ testMouseClick("t7", 100 + 11*mm, 10, "body", "covered div is not clickable");
+ testMouseClick("t7", 10, 10, "t7_over", "covered div is not clickable even within its bounds");
+ setShowing("t7", false);
+ setShowing("t7_over", false);
+
+ // Check that contenteditable elements are considered clickable for fluffing.
+ setShowing("t8", true);
+ var rect = document.getElementById("t8").getBoundingClientRect();
+ testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for mouse input");
+ testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for touch input", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+ setShowing("t8", false);
+
+ // Check that elements are touchable
+ setShowing("t9", true);
+ var rect = document.getElementById("t9").getBoundingClientRect();
+ testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with mouse input");
+ testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with touch input", {
+ inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ });
+ setShowing("t9", false);
+
+ setShowing("t10_over", true);
+ setShowing("t10_left", true);
+ setShowing("t10_right", true);
+ setShowing("t10_top", true);
+ setShowing("t10_bottom", true);
+ testMouseClick("t10_left", 51, 10, "t10_over", "element outside of visible area is not selected");
+ if (self.frameElement &&
+ (self.frameElement.offsetLeft + self.innerWidth >
+ SpecialPowers.wrap(top).innerWidth)) {
+ info("WARNING: Window is too narrow, can't test t10_right");
+ } else {
+ testMouseClick("t10_right", 49, 10, "t10_over", "element outside of visible area is not selected");
+ }
+ testMouseClick("t10_top", 10, 51, "t10_over", "element outside of visible area is not selected");
+ if (self.frameElement &&
+ (self.frameElement.offsetTop + self.innerHeight >
+ SpecialPowers.wrap(top).innerHeight)) {
+ info("WARNING: Window is too short, can't test t10_bottom");
+ } else {
+ testMouseClick("t10_bottom", 10, 49, "t10_over", "element outside of visible area is not selected");
+ }
+ setShowing("t10_over", false);
+ setShowing("t10_left", false);
+ setShowing("t10_right", false);
+ setShowing("t10_top", false);
+ setShowing("t10_bottom", false);
+
+ // Not yet tested:
+ // -- visited link weight
+ // -- "Closest" using Euclidean distance
+ endTest();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_flush_on_paint.html b/layout/base/tests/test_flush_on_paint.html
new file mode 100644
index 000000000..de088b4de
--- /dev/null
+++ b/layout/base/tests/test_flush_on_paint.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that we flush before painting</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="doIteration()">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<embed type="application/x-test" id="plugin" drawmode="solid" style="width:200px; height:200px;"></embed>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test deals with painting and invalidation. " +
+ "Those are tricky to detect, so we have to poll here, which means that we have to rely on flaky timeouts. " +
+ "This special case is safe because we're only polling for events.");
+
+var iterations = 0;
+var plugin = document.getElementById("plugin");
+var lastPaintCount;
+var expectedWidth;
+
+var toggle = true;
+function invalidationLoop() {
+ toggle = !toggle;
+ var color = toggle ? "8F" : "00";
+ plugin.setColor("FFFFFF" + color);
+ setTimeout(invalidationLoop, 20);
+}
+invalidationLoop();
+
+function doIteration() {
+ lastPaintCount = window.mozPaintCount;
+ ok(true, "Beginning iteration " + iterations + ", last paint count: " + lastPaintCount);
+
+ expectedWidth = 201 + iterations;
+ plugin.style.width = expectedWidth + "px";
+ checkDone();
+}
+
+function checkDone() {
+ ok(true, "Check to see if we're done: " + window.mozPaintCount);
+ if (window.mozPaintCount == lastPaintCount) {
+ setTimeout(checkDone, 30);
+ return;
+ }
+
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ is(plugin.getWidthAtLastPaint(), utils.screenPixelsPerCSSPixel*expectedWidth,
+ "Check that we set width before painting");
+
+ ++iterations;
+ if (iterations < 100) {
+ doIteration();
+ } else {
+ SimpleTest.finish();
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html b/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html
new file mode 100644
index 000000000..e6339d94b
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1110277
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1110277</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .testspan {
+ color: yellow;
+ }
+ .testspan[attributestate],
+ .testspan[attributestate]::before, .testspan[attributestate]::after {
+ color: blue;
+ }
+
+ #firstlinetest::first-line {
+ color: purple;
+ }
+ #firstlinetest > .testspan::before {
+ content: "[*]";
+ }
+
+ #aftertest > .testspan::after {
+ content: "[*]";
+ }
+ </style>
+ <script type="application/javascript">
+
+ /** Test for Bug 1110277 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ runtest("first line test", "#firstlinetest > .testspan");
+ runtest("after test", "#aftertest > .testspan");
+ SimpleTest.finish();
+ }
+
+ function runtest(description, selector) {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ var span = document.querySelector(selector);
+ var cs = getComputedStyle(span, "");
+
+ var startcolor = cs.color;
+ var startcount = utils.framesConstructed;
+ is(startcolor, "rgb(255, 255, 0)", description + ": initial color");
+
+ span.setAttribute("attributestate", "true");
+
+ var endcolor = cs.color;
+ var endcount = utils.framesConstructed;
+ is(endcolor, "rgb(0, 0, 255)", description + ": final color");
+ is(endcount, startcount,
+ description + ": should not do frame construction")
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1110277">Mozilla Bug 1110277</a>
+<div id="firstlinetest">
+ <span class="testspan">This <span style="display:block">is a</span> test.</span>
+</div>
+<div id="aftertest">
+ <span class="testspan">This <span style="display:block">is a</span> test.</span>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_scroll_restore.html b/layout/base/tests/test_frame_reconstruction_scroll_restore.html
new file mode 100644
index 000000000..69758b9c1
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_scroll_restore.html
@@ -0,0 +1,68 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268195
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1268195</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ }
+
+ .noscroll {
+ overflow: hidden;
+ height: 100%;
+ }
+
+ /* Toggling this on and off triggers a frame reconstruction on the <body> */
+ html.reconstruct-body::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ // Make sure we have the right scroll element
+ SimpleTest.is(document.body.scrollTopMax > 0, true, "Body is the scrolling element");
+
+ // Scroll to the bottom
+ document.body.scrollTop = document.body.scrollTopMax;
+ SimpleTest.is(document.body.scrollTop > 0, true, "Scrolled body");
+
+ // Do a frame reconstruction on the body while also shortening the
+ // height.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '1px';
+ SimpleTest.is(document.body.scrollTop, 0, "Scroll forced to top");
+
+ // Do another frame reconstruction while lengthening the height again.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '5000px';
+ SimpleTest.is(document.body.scrollTop, 0, "Scroll remained at top");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="setTimeout(run, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268195">Mozilla Bug 1268195</a><br/>
+The scroll position should end the top of the page. This is the top, yay!
+<div id="spacer" style="height: 5000px"></div>
+The scroll position should end the top of the page. This is the bottom!
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html
new file mode 100644
index 000000000..7ec4f7b56
--- /dev/null
+++ b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html
@@ -0,0 +1,713 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest()">
+<p id="display"></p>
+<script>
+// Global variables we want eval() to be able to reference from anywhere
+var f1d;
+var text;
+var suppressedText;
+var suppressedText2;
+var comment;
+var fragment;
+var openedWindow;
+var zeroPoint;
+var zeroRect;
+var zeroQuad;
+var notInDocument = document.createElement('div');
+
+function isEval(expr, b) {
+ is(eval(expr), b, expr);
+}
+
+function isApprox(a, b, msg, options) {
+ if (a != b && 'tolerance' in options &&
+ Math.abs(a - b) < options.tolerance) {
+ ok(true, msg + "(" + a + " within " + options.tolerance + " of " + b + ")");
+ return;
+ }
+ is(a, b, msg);
+}
+
+function makeQuadsExpr(fromStr, options) {
+ var getBoxQuadsOptionParts = [];
+ if ('box' in options) {
+ getBoxQuadsOptionParts.push("box:'" + options.box + "'");
+ }
+ if ('toStr' in options) {
+ getBoxQuadsOptionParts.push("relativeTo:" + options.toStr);
+ }
+ return fromStr + ".getBoxQuads({" + getBoxQuadsOptionParts.join(',') + "})";
+}
+
+function makePointExpr(fromStr, options, x, y) {
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+ return ('toStr' in options ? options.toStr : "document") +
+ ".convertPointFromNode(new DOMPoint(" + x + "," + y + ")," + fromStr + ",{" +
+ convertPointOptionParts.join(",") + "})";
+}
+
+function checkConvertPoints(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].bounds.width;
+ var boxHeight = selfQuads[0].bounds.height;
+
+ var convertTopLeftPointExpr = makePointExpr(fromStr, options, 0, 0);
+ var topLeft = eval(convertTopLeftPointExpr);
+ isApprox(topLeft.x, x1, convertTopLeftPointExpr + ".x", options);
+ isApprox(topLeft.y, y1, convertTopLeftPointExpr + ".y", options);
+
+ var convertTopRightPointExpr = makePointExpr(fromStr, options, boxWidth, 0);
+ var topRight = eval(convertTopRightPointExpr);
+ isApprox(topRight.x, x2, convertTopRightPointExpr + ".x", options);
+ isApprox(topRight.y, y2, convertTopRightPointExpr + ".y", options);
+
+ var convertBottomRightPointExpr = makePointExpr(fromStr, options, boxWidth, boxHeight);
+ var bottomRight = eval(convertBottomRightPointExpr);
+ isApprox(bottomRight.x, x3, convertBottomRightPointExpr + ".x", options);
+ isApprox(bottomRight.y, y3, convertBottomRightPointExpr + ".y", options);
+
+ var convertBottomLeftPointExpr = makePointExpr(fromStr, options, 0, boxHeight);
+ var bottomLeft = eval(convertBottomLeftPointExpr);
+ isApprox(bottomLeft.x, x4, convertBottomLeftPointExpr + ".x", options);
+ isApprox(bottomLeft.y, y4, convertBottomLeftPointExpr + ".y", options);
+}
+
+function checkConvertRect(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].bounds.width;
+ var boxHeight = selfQuads[0].bounds.height;
+
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+
+ var convertRectExpr = ('toStr' in options ? options.toStr : "document") +
+ ".convertRectFromNode(new DOMRect(0,0," + boxWidth + "," + boxHeight + ")," +
+ fromStr + ",{" + convertPointOptionParts.join(",") + "})";
+ var quad = eval(convertRectExpr);
+ isApprox(quad.p1.x, x1, convertRectExpr + ".p1.x", options);
+ isApprox(quad.p1.y, y1, convertRectExpr + ".p1.y", options);
+ isApprox(quad.p2.x, x2, convertRectExpr + ".p2.x", options);
+ isApprox(quad.p2.y, y2, convertRectExpr + ".p2.y", options);
+ isApprox(quad.p3.x, x3, convertRectExpr + ".p3.x", options);
+ isApprox(quad.p3.y, y3, convertRectExpr + ".p3.y", options);
+ isApprox(quad.p4.x, x4, convertRectExpr + ".p4.x", options);
+ isApprox(quad.p4.y, y4, convertRectExpr + ".p4.y", options);
+}
+
+function checkConvertQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].bounds.width;
+ var boxHeight = selfQuads[0].bounds.height;
+
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+
+ var convertQuadExpr = ('toStr' in options ? options.toStr : "document") +
+ ".convertQuadFromNode(new DOMQuad(new DOMRect(0,0," + boxWidth + "," + boxHeight + "))," +
+ fromStr + ",{" + convertPointOptionParts.join(",") + "})";
+ var quad = eval(convertQuadExpr);
+ isApprox(quad.p1.x, x1, convertQuadExpr + ".p1.x", options);
+ isApprox(quad.p1.y, y1, convertQuadExpr + ".p1.y", options);
+ isApprox(quad.p2.x, x2, convertQuadExpr + ".p2.x", options);
+ isApprox(quad.p2.y, y2, convertQuadExpr + ".p2.y", options);
+ isApprox(quad.p3.x, x3, convertQuadExpr + ".p3.x", options);
+ isApprox(quad.p3.y, y3, convertQuadExpr + ".p3.y", options);
+ isApprox(quad.p4.x, x4, convertQuadExpr + ".p4.x", options);
+ isApprox(quad.p4.y, y4, convertQuadExpr + ".p4.y", options);
+}
+
+function checkQuadIsRect(fromStr, options, x, y, w, h) {
+ var quadsExpr = makeQuadsExpr(fromStr, options);
+ var quads = eval(quadsExpr);
+ is(quads.length, 1, quadsExpr + " checking quad count");
+ var q = quads[0];
+ isApprox(q.p1.x, x, quadsExpr + " checking quad.p1.x", options);
+ isApprox(q.p1.y, y, quadsExpr + " checking quad.p1.y", options);
+ isApprox(q.p2.x, x + w, quadsExpr + " checking quad.p2.x", options);
+ isApprox(q.p2.y, y, quadsExpr + " checking quad.p2.y", options);
+ isApprox(q.p3.x, x + w, quadsExpr + " checking quad.p3.x", options);
+ isApprox(q.p3.y, y + h, quadsExpr + " checking quad.p3.y", options);
+ isApprox(q.p4.x, x, quadsExpr + " checking quad.p4.x", options);
+ isApprox(q.p4.y, y + h, quadsExpr + " checking quad.p4.y", options);
+
+ isApprox(q.bounds.left, x, quadsExpr + " checking quad.bounds.left", options);
+ isApprox(q.bounds.top, y, quadsExpr + " checking quad.bounds.top", options);
+ isApprox(q.bounds.width, w, quadsExpr + " checking quad.bounds.width", options);
+ isApprox(q.bounds.height, h, quadsExpr + " checking quad.bounds.height", options);
+
+ checkConvertPoints(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+ checkConvertRect(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+ checkConvertQuad(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+}
+
+function checkQuadIsQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var quadsExpr = makeQuadsExpr(fromStr, options);
+ var quads = eval(quadsExpr);
+ is(quads.length, 1, quadsExpr + " checking quad count");
+ var q = quads[0];
+ isApprox(q.p1.x, x1, quadsExpr + " checking quad.p1.x", options);
+ isApprox(q.p1.y, y1, quadsExpr + " checking quad.p1.y", options);
+ isApprox(q.p2.x, x2, quadsExpr + " checking quad.p2.x", options);
+ isApprox(q.p2.y, y2, quadsExpr + " checking quad.p2.y", options);
+ isApprox(q.p3.x, x3, quadsExpr + " checking quad.p3.x", options);
+ isApprox(q.p3.y, y3, quadsExpr + " checking quad.p3.y", options);
+ isApprox(q.p4.x, x4, quadsExpr + " checking quad.p4.x", options);
+ isApprox(q.p4.y, y4, quadsExpr + " checking quad.p4.y", options);
+
+ isApprox(q.bounds.left, Math.min(x1,x2,x3,x4), quadsExpr + " checking quad.bounds.left", options);
+ isApprox(q.bounds.top, Math.min(y1,y2,y3,y4), quadsExpr + " checking quad.bounds.top", options);
+ isApprox(q.bounds.right, Math.max(x1,x2,x3,x4), quadsExpr + " checking quad.bounds.right", options);
+ isApprox(q.bounds.bottom, Math.max(y1,y2,y3,y4), quadsExpr + " checking quad.bounds.bottom", options);
+
+ checkConvertPoints(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+ checkConvertRect(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+ checkConvertQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+}
+
+function checkException(expr, name) {
+ try {
+ eval(expr);
+ ok(false, "Exception should have been thrown for " + expr);
+ } catch (ex) {
+ is(ex.name, name, "Checking exception type for " + expr);
+ }
+}
+
+function checkNotFound(fromStr, toStr, x1, y1, x2, y2) {
+ var convertPointExpr = toStr + ".convertPointFromNode(new DOMPoint(" + x1 +
+ "," + y1 + ")," + fromStr + ")";
+ checkException(convertPointExpr, "NotFoundError");
+
+ var convertRectExpr = toStr + ".convertRectFromNode(new DOMRect(" + x1 +
+ "," + y1 + "," + x2 + "," + y2 + ")," + fromStr + ")";
+ checkException(convertRectExpr, "NotFoundError");
+
+ var convertQuadExpr = toStr + ".convertQuadFromNode(new DOMQuad(new DOMRect(" + x1 +
+ "," + y1 + "," + x2 + "," + y2 + "))," + fromStr + ")";
+ checkException(convertQuadExpr, "NotFoundError");
+}
+</script>
+<style>
+em {
+ display:inline-block; height:10px; background:gray;
+}
+</style>
+<div id="dContainer"
+ style="padding:13px 14px 15px 16px;
+ border-width:17px 18px 19px 20px; border-style:solid; border-color:yellow;
+ margin:21px 22px 23px 24px;">
+ <div id="d"
+ style="width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue;">
+ </div>
+</div>
+
+<div id="dUnrelated" style="width:50px; height:50px;"></div>
+
+<iframe id="f1" style="width:50px; height:50px; border:0; background:lime;"
+ src="data:text/html,<!DOCTYPE HTML><html style='padding:25px'><div id='f1d' style='position:absolute; left:14px; top:15px; width:16px; height:17px; background:pink'></div>">
+</iframe>
+<!--
+It matters that the first part of this span is on the same line as the above <iframe>!
+That ensures the first quad's X position is not equal to the anonymous block's X position.
+-->
+<span id="ibSplit"
+ ><em id="ibSplitPart1" style="width:100px;"></em
+ ><div style="width:110px; height:20px; background:black"></div
+ ><em style="width:130px;"></em></span>
+
+<table cellspacing="0" id="table" style="border:0; margin:8px; padding:0; background:orange">
+ <tbody style="padding:0; margin:0; border:0; background:blue">
+ <tr style="height:50px; padding:0; margin:0; border:0">
+ <td style="border:0; margin:0; padding:0">Cell</td>
+ </tr>
+ </tbody>
+ <caption style="height:40px; background:yellow">Caption</caption>
+</table>
+
+<div style="height:80px; -moz-column-count:2; -moz-column-fill:auto; border:2px solid black;">
+ <div style="height:20px;"></div>
+ <div id="colSplit" style="height:80px; background:blue; border:10px solid red; border-bottom-width:15px"></div>
+</div>
+
+<div style="width:200px; border:2px solid black;"
+ ><em style="width:150px;"></em
+ ><span id="inlineSplit" style="background:pink; border:10px solid red; border-right-width:15px"
+ ><em style="width:20px; background:green"></em><em style="width:60px"></em
+ ></span
+></div>
+
+<div style="width:200px; border:2px solid black;"
+ ><em style="width:150px;"></em
+ ><span id="textContainer">T
+TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText</span
+></div>
+
+<div id="suppressedTextContainer"> </div>
+<div id="suppressedTextContainer2"> </div>
+
+<div id="commentContainer"><!-- COMMENT --></div>
+
+<div id="displayNone" style="display:none"></div>
+
+<div id="overflowHidden"
+ style="overflow:hidden; width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue;">
+ <div style="height:400px; background:lime;"></div>
+</div>
+
+<div id="overflowScroll"
+ style="overflow:scroll; width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue; background-clip:content-box;">
+ <div id="overflowScrollChild" style="height:400px;"></div>
+</div>
+
+<div id="scaleTransformContainer" style="width:200px; height:200px;">
+ <div id="scaleTransform"
+ style="transform:scale(2); transform-origin:top left; width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="translateTransformContainer" style="width:200px; height:200px;">
+ <div id="translateTransform"
+ style="transform:translate(30px,40px); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="rotateTransformContainer" style="width:200px; height:200px;">
+ <div id="rotateTransform"
+ style="transform:rotate(90deg); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="flipTransformContainer" style="width:200px; height:200px;">
+ <div id="flipTransform"
+ style="transform:scaleY(-1); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="rot45TransformContainer" style="width:200px; height:200px;">
+ <div id="rot45Transform"
+ style="transform:rotate(45deg); width:100px; height:100px; background:yellow"></div>
+</div>
+
+<div id="singularTransform" style="transform:scale(0); width:200px; height:200px;">
+ <div id="singularTransformChild1" style="height:50px;"></div>
+ <div id="singularTransformChild2" style="height:50px;"></div>
+</div>
+
+<div id="threeDTransformContainer" style="perspective:600px; width:200px; height:200px">
+ <div id="threeDTransform" style="transform:rotateY(70deg); background:yellow; height:100px; perspective:600px">
+ <div id="threeDTransformChild" style="transform:rotateY(-70deg); background:blue; height:50px;"></div>
+ </div>
+</div>
+
+<div id="preserve3DTransformContainer" style="perspective:600px; width:200px; height:200px">
+ <div id="preserve3DTransform" style="transform:rotateY(70deg); transform-style:preserve-3d; background:yellow; height:100px;">
+ <div id="preserve3DTransformChild" style="transform:rotateY(-70deg); background:blue; height:50px;"></div>
+ </div>
+</div>
+
+<div id="svgContainer">
+ <svg id="svg" style="width:200px; height:200px; background:lightgray; border:7px solid blue; padding:4px">
+ <circle id="circle" cx="50" cy="50" r="20" fill="red" style="margin:20px; padding:10px; border:15px solid black"></circle>
+ <g transform="scale(2)">
+ <foreignObject x="50" y="20">
+ <div id="foreign" style="width:100px; height:60px; background:purple"></div>
+ </foreignObject>
+ </g>
+ </svg>
+</div>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+window.scrollTo(0,0);
+
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set": [["layout.css.DOMPoint.enabled", true],
+ ["layout.css.DOMQuad.enabled", true],
+ ["layout.css.getBoxQuads.enabled", true],
+ ["layout.css.convertFromNode.enabled", true]]}, grabFeatures);
+}
+
+// This is a bit of a hack but it works. Our Window object was set up while
+// the prefs might have been false so it may not have the features we're
+// testing. Create an <iframe> whose Window is initialized while the prefs are
+// true, and we can steal the features from that Window.
+// When these prefs are enabled on all builds by default, we can skip this step.
+function grabFeatures() {
+ var x = document.createElement('iframe');
+ x.src = "about:blank";
+ document.body.appendChild(x);
+ function setupFeatures(w) {
+ for (var name of ["getBoxQuads", "convertQuadFromNode", "convertRectFromNode", "convertPointFromNode"]) {
+ w.Text.prototype[name] = x.contentWindow.Text.prototype[name];
+ w.Element.prototype[name] = x.contentWindow.Element.prototype[name];
+ w.Document.prototype[name] = x.contentWindow.Document.prototype[name];
+ }
+ for (var name of ["DOMPoint", "DOMQuad"]) {
+ w[name] = x.contentWindow[name];
+ }
+ }
+ x.onload = function() {
+ setupFeatures(window);
+ setupFeatures(f1.contentWindow);
+ runTest();
+ };
+}
+
+function runTest() {
+ zeroPoint = new DOMPoint(0,0);
+ zeroRect = new DOMRect(0,0,0,0);
+ zeroQuad = new DOMQuad(zeroRect);
+
+ // Setup globals
+ f1d = f1.contentWindow.f1d;
+ text = textContainer.firstChild;
+ suppressedText = suppressedTextContainer.firstChild;
+ suppressedText2 = suppressedTextContainer2.firstChild;
+ comment = commentContainer.firstChild;
+ fragment = document.createDocumentFragment();
+
+ // Test basic BoxQuadOptions.box.
+ var dX = d.getBoundingClientRect().left;
+ var dY = d.getBoundingClientRect().top;
+ var dW = d.getBoundingClientRect().width;
+ var dH = d.getBoundingClientRect().height;
+
+ checkQuadIsRect("d", {box:"content"},
+ dX + 4 + 8, dY + 1 + 5, 120, 90);
+ checkQuadIsRect("d", {box:"padding"},
+ dX + 8, dY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("d", {box:"border"},
+ dX, dY, dW, dH);
+ checkQuadIsRect("d", {},
+ dX, dY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("d", {box:"margin"},
+ dX - 12, dY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test basic BoxQuadOptions.relativeTo
+ checkQuadIsRect("d", {toStr:"dContainer"},
+ 12 + 16 + 20, 9 + 13 + 17, dW, dH);
+
+ // Test BoxQuadOptions.relativeTo relative to this document
+ checkQuadIsRect("d", {toStr:"document"},
+ dX, dY, dW, dH);
+ // Test BoxQuadOptions.relativeTo relative to a non-ancestor.
+ var dUnrelatedX = dUnrelated.getBoundingClientRect().left;
+ var dUnrelatedY = dUnrelated.getBoundingClientRect().top;
+ checkQuadIsRect("d", {toStr:"dUnrelated"},
+ dX - dUnrelatedX, dY - dUnrelatedY, dW, dH);
+ // Test BoxQuadOptions.relativeTo relative to an element in a different document (and the document)
+ var f1X = f1.getBoundingClientRect().left;
+ var f1Y = f1.getBoundingClientRect().top;
+ checkQuadIsRect("d", {toStr:"f1.contentWindow.f1d"},
+ dX - (f1X + 14), dY - (f1Y + 15), dW, dH);
+ checkQuadIsRect("d", {toStr:"f1.contentDocument"},
+ dX - f1X, dY - f1Y, dW, dH);
+ // Test one document relative to another
+ checkQuadIsRect("f1.contentDocument", {toStr:"document"},
+ f1X, f1Y, 50, 50);
+ // The box type is irrelevant for a document
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"content"},
+ f1X, f1Y, 50, 50);
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"margin"},
+ f1X, f1Y, 50, 50);
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"padding"},
+ f1X, f1Y, 50, 50);
+
+ // Test that anonymous boxes are correctly ignored when building quads.
+ var ibSplitPart1X = ibSplitPart1.getBoundingClientRect().left;
+ var ibSplitY = ibSplit.getBoundingClientRect().top;
+ isEval("ibSplit.getBoxQuads().length", 3);
+ isEval("ibSplit.getBoxQuads()[0].bounds.left", ibSplitPart1X);
+ isEval("ibSplit.getBoxQuads()[0].bounds.width", 100);
+ isEval("ibSplit.getBoxQuads()[1].bounds.width", 110);
+ isEval("ibSplit.getBoxQuads()[2].bounds.width", 130);
+ isEval("table.getBoxQuads().length", 2);
+ isEval("table.getBoxQuads()[0].bounds.height", 50);
+ isEval("table.getBoxQuads()[1].bounds.height", 40);
+
+ // Test that we skip anonymous boxes when finding the right box to be relative to.
+ checkQuadIsRect("d", {toStr:"ibSplit"},
+ dX - ibSplitPart1X, dY - ibSplitY, dW, dH);
+ var tableX = table.getClientRects()[0].left;
+ var tableY = table.getClientRects()[0].top;
+ checkQuadIsRect("d", {toStr:"table"},
+ dX - tableX, dY - tableY, dW, dH);
+ isEval("ibSplit.convertPointFromNode(zeroPoint,d).x", dX - ibSplitPart1X);
+ isEval("table.convertPointFromNode(zeroPoint,d).x", dX - table.getClientRects()[0].left);
+
+ // Test boxes generated by block splitting. Check for borders being placed correctly.
+ var colSplitY = colSplit.getClientRects()[0].top;
+ isEval("colSplit.getBoxQuads().length", 2);
+ isEval("colSplit.getBoxQuads()[0].bounds.top", colSplitY);
+ isEval("colSplit.getBoxQuads()[0].bounds.height", 60);
+ isEval("colSplit.getBoxQuads()[1].bounds.top", colSplitY - 20);
+ isEval("colSplit.getBoxQuads()[1].bounds.height", 45);
+ isEval("colSplit.getBoxQuads({box:'content'}).length", 2);
+ // The first box for the block has the top border; the second box has the bottom border.
+ isEval("colSplit.getBoxQuads({box:'content'})[0].bounds.top", colSplitY + 10);
+ isEval("colSplit.getBoxQuads({box:'content'})[0].bounds.height", 50);
+ isEval("colSplit.getBoxQuads({box:'content'})[1].bounds.top", colSplitY - 20);
+ isEval("colSplit.getBoxQuads({box:'content'})[1].bounds.height", 30);
+
+ var inlineSplitX = inlineSplit.getClientRects()[0].left;
+ isEval("inlineSplit.getBoxQuads().length", 2);
+ isEval("inlineSplit.getBoxQuads()[0].bounds.left", inlineSplitX);
+ isEval("inlineSplit.getBoxQuads()[0].bounds.width", 30);
+ isEval("inlineSplit.getBoxQuads()[1].bounds.left", inlineSplitX - 150);
+ isEval("inlineSplit.getBoxQuads()[1].bounds.width", 75);
+ isEval("inlineSplit.getBoxQuads({box:'content'}).length", 2);
+ // The first box for the inline has the left border; the second box has the right border.
+ isEval("inlineSplit.getBoxQuads({box:'content'})[0].bounds.left", inlineSplitX + 10);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[0].bounds.width", 20);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[1].bounds.left", inlineSplitX - 150);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[1].bounds.width", 60);
+
+ var textX = textContainer.getClientRects()[0].left;
+ isEval("text.getBoxQuads().length", 2);
+ isEval("text.getBoxQuads()[0].bounds.left", textX);
+ isEval("text.getBoxQuads()[1].bounds.left", textX - 150);
+ // Box types are irrelevant for text
+ isEval("text.getBoxQuads({box:'content'}).length", 2);
+ isEval("text.getBoxQuads({box:'content'})[0].bounds.left", textX);
+ isEval("text.getBoxQuads({box:'content'})[1].bounds.left", textX - 150);
+ isEval("text.getBoxQuads({box:'padding'}).length", 2);
+ isEval("text.getBoxQuads({box:'padding'})[0].bounds.left", textX);
+ isEval("text.getBoxQuads({box:'padding'})[1].bounds.left", textX - 150);
+ isEval("text.getBoxQuads({box:'margin'}).length", 2);
+ isEval("text.getBoxQuads({box:'margin'})[0].bounds.left", textX);
+ isEval("text.getBoxQuads({box:'margin'})[1].bounds.left", textX - 150);
+
+ // Test table margins
+ isEval("table.getBoxQuads({box:'margin'}).length", 1);
+ isEval("table.getBoxQuads({box:'margin'})[0].bounds.height", 106);
+
+ // Check that a text node whose layout might have been optimized away gives
+ // correct results.
+ var suppressedTextContainerX = suppressedTextContainer.getBoundingClientRect().left;
+ isEval("suppressedText.getBoxQuads().length", 1);
+ isEval("suppressedText.getBoxQuads()[0].bounds.left", suppressedTextContainerX);
+ isEval("suppressedText.getBoxQuads()[0].bounds.width", 0);
+
+ var suppressedTextContainer2X = suppressedTextContainer2.getBoundingClientRect().left;
+ isEval("document.convertPointFromNode(zeroPoint,suppressedText2).x",
+ suppressedTextContainer2X);
+
+ checkException("comment.getBoxQuads()", "TypeError");
+ checkException("d.getBoxQuads({relativeTo:comment})", "TypeError");
+ checkException("comment.convertPointFromNode(zeroPoint,document)", "TypeError");
+ checkException("document.convertPointFromNode(zeroPoint,comment)", "TypeError");
+ checkException("comment.convertRectFromNode(zeroRect,document)", "TypeError");
+ checkException("document.convertRectFromNode(zeroRect,comment)", "TypeError");
+ checkException("comment.convertQuadFromNode(zeroQuad,document)", "TypeError");
+ checkException("document.convertQuadFromNode(zeroQuad,comment)", "TypeError");
+
+ checkException("fragment.getBoxQuads()", "TypeError");
+ checkException("d.getBoxQuads({relativeTo:fragment})", "TypeError");
+ checkException("fragment.convertPointFromNode(zeroPoint,document)", "TypeError");
+ checkException("document.convertPointFromNode(zeroPoint,fragment)", "TypeError");
+ checkException("fragment.convertRectFromNode(zeroRect,document)", "TypeError");
+ checkException("document.convertRectFromNode(zeroRect,fragment)", "TypeError");
+ checkException("fragment.convertQuadFromNode(zeroQuad,document)", "TypeError");
+ checkException("document.convertQuadFromNode(zeroQuad,fragment)", "TypeError");
+
+ isEval("displayNone.getBoxQuads().length", 0);
+ isEval("notInDocument.getBoxQuads().length", 0);
+ checkNotFound("displayNone", "document", 1, 2, 3, 4);
+ checkNotFound("notInDocument", "document", 1, 2, 3, 4);
+ checkNotFound("document", "displayNone", 1, 2, 3, 4);
+ checkNotFound("document", "notInDocument", 1, 2, 3, 4);
+
+ // Test an overflow:hidden version of d. overflow:hidden should not affect
+ // the quads, basically.
+ var oHX = overflowHidden.getBoundingClientRect().left;
+ var oHY = overflowHidden.getBoundingClientRect().top;
+ checkQuadIsRect("overflowHidden", {box:"content"},
+ oHX + 4 + 8, oHY + 1 + 5, 120, 90);
+ checkQuadIsRect("overflowHidden", {box:"padding"},
+ oHX + 8, oHY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("overflowHidden", {box:"border"},
+ oHX, oHY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowHidden", {},
+ oHX, oHY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowHidden", {box:"margin"},
+ oHX - 12, oHY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test an overflow:scroll version of d. I assume that boxes aren't affected
+ // by the scrollbar although it's not clear that this is correct.
+ var oSX = overflowScroll.getBoundingClientRect().left;
+ var oSY = overflowScroll.getBoundingClientRect().top;
+ checkQuadIsRect("overflowScroll", {box:"content"},
+ oSX + 4 + 8, oSY + 1 + 5, 120, 90);
+ checkQuadIsRect("overflowScroll", {box:"padding"},
+ oSX + 8, oSY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("overflowScroll", {box:"border"},
+ oSX, oSY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowScroll", {},
+ oSX, oSY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowScroll", {box:"margin"},
+ oSX - 12, oSY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test simple 2D transforms.
+ var stcX = scaleTransformContainer.getBoundingClientRect().left;
+ var stcY = scaleTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("scaleTransform", {},
+ stcX, stcY, 140, 160);
+ var ttcX = translateTransformContainer.getBoundingClientRect().left;
+ var ttcY = translateTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("translateTransform", {},
+ ttcX + 30, ttcY + 40, 70, 80);
+ // Test mapping into a transformed element.
+ checkQuadIsRect("scaleTransform", {toStr:"translateTransform"},
+ stcX - (ttcX + 30), stcY - (ttcY + 40), 140, 160);
+ // Test 90 degree rotation.
+ var rotatetcX = rotateTransformContainer.getBoundingClientRect().left;
+ var rotatetcY = rotateTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("rotateTransform", {},
+ rotatetcX + 75, rotatetcY + 5,
+ rotatetcX + 75, rotatetcY + 75,
+ rotatetcX - 5, rotatetcY + 75,
+ rotatetcX - 5, rotatetcY + 5);
+ // Test vertical flip.
+ var fliptcX = flipTransformContainer.getBoundingClientRect().left;
+ var fliptcY = flipTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("flipTransform", {},
+ fliptcX, fliptcY + 80,
+ fliptcX + 70, fliptcY + 80,
+ fliptcX + 70, fliptcY,
+ fliptcX, fliptcY);
+ // Test non-90deg rotation.
+ var rot45tcX = rot45TransformContainer.getBoundingClientRect().left;
+ var rot45tcY = rot45TransformContainer.getBoundingClientRect().top;
+ var halfDiagonal = 100/Math.sqrt(2);
+ checkQuadIsQuad("rot45Transform", {tolerance:0.01},
+ rot45tcX + 50, rot45tcY + 50 - halfDiagonal,
+ rot45tcX + 50 + halfDiagonal, rot45tcY + 50,
+ rot45tcX + 50, rot45tcY + 50 + halfDiagonal,
+ rot45tcX + 50 - halfDiagonal, rot45tcY + 50);
+
+ // Test singular transforms.
+ var singularTransformX = singularTransform.getBoundingClientRect().left;
+ var singularTransformY = singularTransform.getBoundingClientRect().top;
+ // They map everything to a point.
+ checkQuadIsRect("singularTransform", {},
+ singularTransformX, singularTransformY, 0, 0);
+ checkQuadIsRect("singularTransformChild2", {},
+ singularTransformX, singularTransformY, 0, 0);
+ // Mapping into an element with a singular transform from outside sets
+ // everything to zero.
+ checkQuadIsRect("d", {toStr:"singularTransform"},
+ 0, 0, 0, 0);
+ // But mappings within a subtree of an element with a singular transform work.
+ checkQuadIsRect("singularTransformChild2", {toStr:"singularTransformChild1"},
+ 0, 50, 200, 50);
+
+ // Test 3D transforms.
+ var t3tcX = threeDTransformContainer.getBoundingClientRect().left;
+ var t3tcY = threeDTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("threeDTransform", {tolerance:0.01},
+ t3tcX + 59.446714, t3tcY - 18.569847,
+ t3tcX + 129.570778, t3tcY + 13.540874,
+ t3tcX + 129.570778, t3tcY + 100,
+ t3tcX + 59.446714, t3tcY + 100);
+ // Test nested 3D transforms (without preserve-3d).
+ checkQuadIsQuad("threeDTransformChild", {tolerance:0.01},
+ t3tcX + 89.395061, t3tcY + 2.243033,
+ t3tcX + 113.041727, t3tcY - 2.758530,
+ t3tcX + 113.041727, t3tcY + 52.985921,
+ t3tcX + 89.395061, t3tcY + 47.571899);
+ // Test preserve-3D.
+ var p3dtcX = preserve3DTransformContainer.getBoundingClientRect().left;
+ var p3dtcY = preserve3DTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("preserve3DTransformChild", {tolerance:0.01},
+ p3dtcX, p3dtcY, 200, 50,
+ {tolerance:0.0001});
+ // Test mapping back into preserve-3D.
+ checkQuadIsRect("d", {toStr:"preserve3DTransformChild",tolerance:0.01},
+ dX - p3dtcX, dY - p3dtcY, dW, dH);
+
+ // Test SVG.
+ var svgContainerX = svgContainer.getBoundingClientRect().left;
+ var svgContainerY = svgContainer.getBoundingClientRect().top;
+ checkQuadIsRect("circle", {},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ // Box types are ignored for SVG elements.
+ checkQuadIsRect("circle", {box:"content"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("circle", {box:"padding"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("circle", {box:"margin"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("d", {toStr:"circle"},
+ dX - (svgContainerX + 41), dY - (svgContainerY + 41), dW, dH);
+ // Test foreignObject inside an SVG transform.
+ checkQuadIsRect("foreign", {},
+ svgContainerX + 111, svgContainerY + 51, 200, 120);
+ // Outer <svg> elements support padding and content boxes
+ checkQuadIsRect("svg", {box:"border"},
+ svgContainerX, svgContainerY, 222, 222);
+ checkQuadIsRect("svg", {box:"padding"},
+ svgContainerX + 7, svgContainerY + 7, 208, 208);
+ checkQuadIsRect("svg", {box:"content"},
+ svgContainerX + 11, svgContainerY + 11, 200, 200);
+
+ // XXX Test SVG text (probably broken; unclear what the best way is to handle it)
+
+ // Test that converting between nodes in different toplevel browsing contexts
+ // throws an exception.
+ try {
+ openedWindow = window.open("data:text/html,<div id='d'>","");
+ } catch (ex) {
+ // in some cases we can't open the window.
+ openedWindow = null;
+ }
+ if (openedWindow) {
+ openedWindow.addEventListener("load", function() {
+ checkException("openedWindow.d.getBoxQuads({relativeTo:document})", "NotFoundError");
+ checkException("document.getBoxQuads({relativeTo:openedWindow.d})", "NotFoundError");
+ checkException("openedWindow.d.convertPointFromNode(zeroPoint,document)", "NotFoundError");
+ checkException("document.convertPointFromNode(zeroPoint,openedWindow.d)", "NotFoundError");
+ checkException("openedWindow.d.convertRectFromNode(zeroRect,document)", "NotFoundError");
+ checkException("document.convertRectFromNode(zeroRect,openedWindow.d)", "NotFoundError");
+ checkException("openedWindow.d.convertQuadFromNode(zeroQuad,document)", "NotFoundError");
+ checkException("document.convertQuadFromNode(zeroQuad,openedWindow.d)", "NotFoundError");
+ openedWindow.close();
+ SimpleTest.finish();
+ });
+ } else {
+ SimpleTest.finish();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_getClientRects_emptytext.html b/layout/base/tests/test_getClientRects_emptytext.html
new file mode 100644
index 000000000..c009a190e
--- /dev/null
+++ b/layout/base/tests/test_getClientRects_emptytext.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<div id="testDiv"> </div>
+<script>
+var textNode = testDiv.firstChild;
+var range = new Range();
+range.selectNodeContents(textNode);
+is(range.getClientRects().length, 1, "Text node should have a rectangle");
+var rect = range.getClientRects()[0];
+ok(rect.left > 0, "Rectangle x should be greater than zero");
+ok(rect.top > 0, "Rectangle y should be greater than zero");
+is(rect.width, 0, "Rectangle should be zero width");
+is(rect.height, 0, "Rectangle should be zero height");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_mozPaintCount.html b/layout/base/tests/test_mozPaintCount.html
new file mode 100644
index 000000000..4a6c3f714
--- /dev/null
+++ b/layout/base/tests/test_mozPaintCount.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for mozPaintCount</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <script type="application/javascript">
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="doBackgroundFlicker()">
+<p id="display">
+<embed type="application/x-test" width="100" height="100" id="p"
+ drawmode="solid" color="FF00FF00"></embed>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var startPaintCount = window.mozPaintCount;
+ok(true, "Got to initial paint count: " + startPaintCount);
+var color = 0;
+
+function doPluginFlicker() {
+ ok(true, "Plugin color iteration " + color + ", paint count: " + window.mozPaintCount);
+ if (window.mozPaintCount - startPaintCount > 20) {
+ ok(true, "Got enough paints from plugin color changes");
+ SimpleTest.finish();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ var str = color.toString(16);
+ if (str.length < 2) {
+ str = "0" + str;
+ }
+ str = "FF" + str + str + str;
+ document.getElementById("p").setColor(str);
+ setTimeout(doPluginFlicker, 0);
+}
+
+function doBackgroundFlicker() {
+ ok(true, "Background color iteration " + color + ", paint count: " + window.mozPaintCount);
+ if (window.mozPaintCount - startPaintCount > 20) {
+ ok(true, "Got enough paints from background color changes");
+ startPaintCount = window.mozPaintCount;
+ doPluginFlicker();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.body.style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doBackgroundFlicker, 0);
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_preserve3d_sorting_hit_testing.html b/layout/base/tests/test_preserve3d_sorting_hit_testing.html
new file mode 100644
index 000000000..15417713c
--- /dev/null
+++ b/layout/base/tests/test_preserve3d_sorting_hit_testing.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=684759">Mozilla Bug 684759</a>
+<iframe src="preserve3d_sorting_hit_testing_iframe.html" id="iframe" height="1000" width="1000" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 684759 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ var iframe = document.getElementById("iframe");
+
+ var doc = iframe.contentDocument;
+
+ var big= doc.getElementById("big");
+ var small = doc.getElementById("small");
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y).id, expected_element.id,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(650, 250, small, "Small object should be infront of big");
+ check(650, 308, big, "Check bounds of small object");
+ check(650, 207, big, "Check bounds of small object");
+ check(607, 250, big, "Check bounds of small object");
+ check(708, 250, big, "Check bounds of small object");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_preserve3d_sorting_hit_testing2.html b/layout/base/tests/test_preserve3d_sorting_hit_testing2.html
new file mode 100644
index 000000000..991b94640
--- /dev/null
+++ b/layout/base/tests/test_preserve3d_sorting_hit_testing2.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1241394
+-->
+<head>
+ <title>Test for Bug 1241394</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1241394">Mozilla Bug 1241394</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1241394 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var win;
+
+ window.child_opened = function(doc) {
+ var container= doc.getElementById("container");
+
+ isnot(doc.elementFromPoint(60, 50).id, container.id,
+ "point (50, 50): should not hit background");
+
+ win.close();
+ SimpleTest.finish();
+ }
+
+ win = window.open("preserve3d_sorting_hit_testing2_iframe.html");
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_reftests_with_caret.html b/layout/base/tests/test_reftests_with_caret.html
new file mode 100644
index 000000000..d9b5d4a6c
--- /dev/null
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -0,0 +1,341 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Reftests with caret drawing</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ iframe {
+ border: none;
+ width: 600px;
+ height: 400px;
+ }
+ </style>
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var iframes = [];
+function callbackTestIframe(iframe)
+{
+ iframes.push(iframe);
+
+ if (iframes.length != 2)
+ return;
+
+ var result = iframes[0];
+ var reference = iframes[1];
+
+ // Using assertSnapshots is important to get the data-URIs of failing tests
+ // dumped into the log in a format that reftest-analyzer.xhtml can process.
+ var passed = assertSnapshots(result.snapshot, reference.snapshot, true,
+ null /*no fuzz*/, result.src, reference.src);
+
+ // Remove the iframes if the test was successful
+ if (passed) {
+ result.parentNode.removeChild(result);
+ reference.parentNode.removeChild(reference);
+ }
+
+ iframes = [];
+ SimpleTest.waitForFocus(nextTest);
+}
+
+function doSnapShot(iframe) {
+ iframe.snapshot = snapshotWindow(iframe.contentWindow, true);
+ callbackTestIframe(iframe);
+};
+
+function remotePageLoaded(callback) {
+ var iframe = this;
+ setTimeout(function(){
+ doSnapShot(iframe);
+ callback();
+ }, 0);
+};
+
+const MAX_ITERATIONS = 1000;
+
+function createIframe(url,next) {
+ var iframe = document.createElement("iframe");
+ iframe.src = url;
+ iframe.remotePageLoaded = remotePageLoaded;
+ var me = this;
+ var currentIteration = 0;
+ function iframeLoadCompleted() {
+ var docEl = iframe.contentDocument.documentElement;
+ if (docEl.className.indexOf("reftest-wait") >= 0) {
+ if (currentIteration++ > MAX_ITERATIONS) {
+ ok(false, "iframe load for " + url + " timed out");
+ endTest();
+ } else {
+ setTimeout(iframeLoadCompleted, 0);
+ }
+ return;
+ }
+ iframe.remotePageLoaded(function() {
+ if (next) {
+ setTimeout(function(){createIframe(next,null);}, 0)
+ }
+ });
+ }
+ iframe.addEventListener("load", iframeLoadCompleted, false);
+ window.document.body.appendChild(iframe);
+ iframe.focus();
+};
+
+function refTest(test,ref) {
+ createIframe(test,ref);
+};
+
+var caretBlinkTime = null;
+function endTest() {
+ var parentDoc = window.parent.document;
+ parentDoc.styleSheets[parentDoc.styleSheets.length-1].deleteRule(0);
+ // finish(), yet let the test actually end first, to be safe.
+ SimpleTest.executeSoon(SimpleTest.finish);
+}
+
+var tests = [
+ [ 'bug106855-1.html' , 'bug106855-1-ref.html' ] ,
+ [ 'bug106855-2.html' , 'bug106855-1-ref.html' ] ,
+ [ 'bug389321-2.html' , 'bug389321-2-ref.html' ] ,
+ [ 'bug613807-1.html' , 'bug613807-1-ref.html' ] ,
+ [ 'bug1082486-1.html', 'bug1082486-1-ref.html'] ,
+ [ 'bug1082486-2.html', 'bug1082486-2-ref.html'] ,
+ // The following test cases are all involving with one sending
+ // synthesizeKey(), the other without. They fail when accessiblecaret
+ // is enabled. Test them with the preference off.
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.accessiblecaret.enabled', false]]}, nextTest);} ,
+ [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] ,
+ [ 'bug240933-2.html' , 'bug240933-1-ref.html' ] ,
+ [ 'bug389321-1.html' , 'bug389321-1-ref.html' ] ,
+ [ 'bug389321-3.html' , 'bug389321-3-ref.html' ] ,
+ [ 'bug482484.html' , 'bug482484-ref.html' ] ,
+ [ 'bug503399.html' , 'bug503399-ref.html' ] ,
+ [ 'bug585922.html' , 'bug585922-ref.html' ] ,
+ [ 'bug597519-1.html' , 'bug597519-1-ref.html' ] ,
+ [ 'bug602141-1.html' , 'bug602141-1-ref.html' ] ,
+ [ 'bug602141-2.html' , 'bug602141-2-ref.html' ] ,
+ [ 'bug602141-3.html' , 'bug602141-3-ref.html' ] ,
+ [ 'bug602141-4.html' , 'bug602141-4-ref.html' ] ,
+ [ 'bug612271-1.html' , 'bug612271-ref.html' ] ,
+ [ 'bug612271-2.html' , 'bug612271-ref.html' ] ,
+ [ 'bug612271-3.html' , 'bug612271-ref.html' ] ,
+ [ 'bug613433-1.html' , 'bug613433-ref.html' ] ,
+ [ 'bug613433-2.html' , 'bug613433-ref.html' ] ,
+ [ 'bug613433-3.html' , 'bug613433-ref.html' ] ,
+ [ 'bug632215-1.html' , 'bug632215-ref.html' ] ,
+ [ 'bug632215-2.html' , 'bug632215-ref.html' ] ,
+ [ 'bug633044-1.html' , 'bug633044-1-ref.html' ] ,
+ [ 'bug634406-1.html' , 'bug634406-1-ref.html' ] ,
+ [ 'bug644428-1.html' , 'bug644428-1-ref.html' ] ,
+ [ 'input-maxlength-valid-before-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-minlength-valid-before-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-maxlength-ui-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-ui-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-minlength-ui-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-ui-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'textarea-maxlength-valid-before-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-minlength-valid-before-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-maxlength-ui-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-ui-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-minlength-ui-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-ui-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ function() {SpecialPowers.pushPrefEnv({'set': [['bidi.browser.ui', true]]}, nextTest);} ,
+ [ 'bug646382-1.html' , 'bug646382-1-ref.html' ] ,
+ [ 'bug646382-2.html' , 'bug646382-2-ref.html' ] ,
+ [ 'bug664087-1.html' , 'bug664087-1-ref.html' ] ,
+ [ 'bug664087-2.html' , 'bug664087-2-ref.html' ] ,
+ [ 'bug682712-1.html' , 'bug682712-1-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['bidi.browser.ui']]}, nextTest);} ,
+ [ 'bug746993-1.html' , 'bug746993-1-ref.html' ] ,
+ [ 'bug956530-1.html' , 'bug956530-1-ref.html' ] ,
+ [ 'bug989012-1.html' , 'bug989012-1-ref.html' ] ,
+ [ 'bug989012-2.html' , 'bug989012-2-ref.html' ] ,
+ [ 'bug989012-3.html' , 'bug989012-3-ref.html' ] ,
+ [ 'bug1007065-1.html' , 'bug1007065-1-ref.html' ] ,
+ [ 'bug1007067-1.html' , 'bug1007067-1-ref.html' ] ,
+ [ 'bug1061468.html' , 'bug1061468-ref.html' ] ,
+ [ 'bug1097242-1.html', 'bug1097242-1-ref.html'] ,
+ [ 'bug1109968-1.html', 'bug1109968-1-ref.html'] ,
+ [ 'bug1109968-2.html', 'bug1109968-2-ref.html'] ,
+ // [ 'bug1123067-1.html' , 'bug1123067-ref.html' ] , TODO: bug 1129205
+ [ 'bug1123067-2.html' , 'bug1123067-ref.html' ] ,
+ [ 'bug1123067-3.html' , 'bug1123067-ref.html' ] ,
+ [ 'bug1132768-1.html' , 'bug1132768-1-ref.html'] ,
+ [ 'bug1237236-1.html' , 'bug1237236-1-ref.html' ] ,
+ [ 'bug1237236-2.html' , 'bug1237236-2-ref.html' ] ,
+ [ 'bug1258308-2.html' , 'bug1258308-2-ref.html' ] ,
+ [ 'bug1259949-1.html' , 'bug1259949-1-ref.html'] ,
+ [ 'bug1259949-2.html' , 'bug1259949-2-ref.html'] ,
+ [ 'bug1263288.html' , 'bug1263288-ref.html'] ,
+ [ 'bug1263357-1.html' , 'bug1263357-1-ref.html'] ,
+ [ 'bug1263357-2.html' , 'bug1263357-2-ref.html'] ,
+ [ 'bug1263357-3.html' , 'bug1263357-3-ref.html'] ,
+ [ 'bug1263357-4.html' , 'bug1263357-4-ref.html'] ,
+ [ 'bug1263357-5.html' , 'bug1263357-5-ref.html'] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
+];
+
+if (navigator.appVersion.indexOf("Android") == -1 &&
+ SpecialPowers.Services.appinfo.name != "B2G") {
+ tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
+ tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
+ tests.push([ 'bug923376.html' , 'bug923376-ref.html' ]);
+ tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);});
+ tests.push([ 'bug966992-1.html' , 'bug966992-1-ref.html' ]);
+ tests.push([ 'bug966992-2.html' , 'bug966992-2-ref.html' ]);
+ tests.push([ 'bug966992-3.html' , 'bug966992-3-ref.html' ]);
+ tests.push(function() {SpecialPowers.pushPrefEnv({'clear': [['layout.css.overflow-clip-box.enabled']]}, nextTest);});
+ tests.push([ 'bug1258308-1.html' , 'bug1258308-1-ref.html' ]); // maybe VK_END doesn't work on Android?
+} else {
+ is(SpecialPowers.getIntPref("layout.spellcheckDefault"), 0, "Spellcheck should be turned off for this platform or this if..else check removed");
+}
+
+if (navigator.platform.indexOf("Linux") >= 0 &&
+ SpecialPowers.Services.appinfo.name != "B2G") {
+ tests = tests.concat([
+ // eDirPrevious, Shift+click
+ [ 'multi-range-user-select.html#prev1S_' , 'multi-range-user-select-ref.html#prev1S_' ] ,
+ [ 'multi-range-user-select.html#prev2S_' , 'multi-range-user-select-ref.html#prev2S_' ] ,
+ [ 'multi-range-user-select.html#prev3S_' , 'multi-range-user-select-ref.html#prev3S_' ] ,
+ [ 'multi-range-user-select.html#prev4S_' , 'multi-range-user-select-ref.html#prev4S_' ] ,
+ [ 'multi-range-user-select.html#prev5S_' , 'multi-range-user-select-ref.html#prev5S_' ] ,
+ [ 'multi-range-user-select.html#prev6S_' , 'multi-range-user-select-ref.html#prev6S_' ] ,
+ [ 'multi-range-user-select.html#prev7S_' , 'multi-range-user-select-ref.html#prev7S_' ] ,
+ // eDirPrevious, Shift+Accel+click
+ [ 'multi-range-user-select.html#prev1SA' , 'multi-range-user-select-ref.html#prev1SA' ] ,
+ [ 'multi-range-user-select.html#prev2SA' , 'multi-range-user-select-ref.html#prev2SA' ] ,
+ [ 'multi-range-user-select.html#prev3SA' , 'multi-range-user-select-ref.html#prev3SA' ] ,
+ [ 'multi-range-user-select.html#prev4SA' , 'multi-range-user-select-ref.html#prev4SA' ] ,
+ [ 'multi-range-user-select.html#prev5SA' , 'multi-range-user-select-ref.html#prev5SA' ] ,
+ [ 'multi-range-user-select.html#prev6SA' , 'multi-range-user-select-ref.html#prev6SA' ] ,
+ [ 'multi-range-user-select.html#prev7SA' , 'multi-range-user-select-ref.html#prev7SA' ] ,
+ // eDirPrevious, Accel+drag-select (adding an additional range)
+ [ 'multi-range-user-select.html#prev1AD' , 'multi-range-user-select-ref.html#prev1AD' ] ,
+ [ 'multi-range-user-select.html#prev7AD' , 'multi-range-user-select-ref.html#prev7AD' ] ,
+ // eDirPrevious, Accel+drag-select (bug 1128722)
+ [ 'multi-range-user-select.html#prev8AD' , 'multi-range-user-select-ref.html#prev8AD' ] ,
+ // eDirPrevious, VK_RIGHT / LEFT
+ [ 'multi-range-user-select.html#prev1SR' , 'multi-range-user-select-ref.html#prev1SR' ] ,
+ [ 'multi-range-user-select.html#prev1SL' , 'multi-range-user-select-ref.html#prev1SL' ] ,
+ // eDirNext, Shift+click
+ [ 'multi-range-user-select.html#next1S_' , 'multi-range-user-select-ref.html#next1S_' ] ,
+ [ 'multi-range-user-select.html#next2S_' , 'multi-range-user-select-ref.html#next2S_' ] ,
+ [ 'multi-range-user-select.html#next3S_' , 'multi-range-user-select-ref.html#next3S_' ] ,
+ [ 'multi-range-user-select.html#next4S_' , 'multi-range-user-select-ref.html#next4S_' ] ,
+ [ 'multi-range-user-select.html#next5S_' , 'multi-range-user-select-ref.html#next5S_' ] ,
+ [ 'multi-range-user-select.html#next6S_' , 'multi-range-user-select-ref.html#next6S_' ] ,
+ [ 'multi-range-user-select.html#next7S_' , 'multi-range-user-select-ref.html#next7S_' ] ,
+ // eDirNext, Shift+Accel+click
+ [ 'multi-range-user-select.html#next1SA' , 'multi-range-user-select-ref.html#next1SA' ] ,
+ [ 'multi-range-user-select.html#next2SA' , 'multi-range-user-select-ref.html#next2SA' ] ,
+ [ 'multi-range-user-select.html#next3SA' , 'multi-range-user-select-ref.html#next3SA' ] ,
+ [ 'multi-range-user-select.html#next4SA' , 'multi-range-user-select-ref.html#next4SA' ] ,
+ [ 'multi-range-user-select.html#next5SA' , 'multi-range-user-select-ref.html#next5SA' ] ,
+ [ 'multi-range-user-select.html#next6SA' , 'multi-range-user-select-ref.html#next6SA' ] ,
+ [ 'multi-range-user-select.html#next7SA' , 'multi-range-user-select-ref.html#next7SA' ] ,
+ // eDirNext, Accel+drag-select (adding an additional range)
+ [ 'multi-range-user-select.html#next1AD' , 'multi-range-user-select-ref.html#next1AD' ] ,
+ [ 'multi-range-user-select.html#next7AD' , 'multi-range-user-select-ref.html#next7AD' ] ,
+ // eDirNext, Accel+drag-select (bug 1128722)
+ [ 'multi-range-user-select.html#next8AD' , 'multi-range-user-select-ref.html#next8AD' ] ,
+ // eDirNext, VK_RIGHT / LEFT
+ [ 'multi-range-user-select.html#next1SR' , 'multi-range-user-select-ref.html#next1SR' ] ,
+ [ 'multi-range-user-select.html#next1SL' , 'multi-range-user-select-ref.html#next1SL' ] ,
+ // eDirPrevious, Shift+click
+ [ 'multi-range-script-select.html#prev1S_' , 'multi-range-script-select-ref.html#prev1S_' ] ,
+ [ 'multi-range-script-select.html#prev2S_' , 'multi-range-script-select-ref.html#prev2S_' ] ,
+ [ 'multi-range-script-select.html#prev3S_' , 'multi-range-script-select-ref.html#prev3S_' ] ,
+ [ 'multi-range-script-select.html#prev4S_' , 'multi-range-script-select-ref.html#prev4S_' ] ,
+ [ 'multi-range-script-select.html#prev5S_' , 'multi-range-script-select-ref.html#prev5S_' ] ,
+ [ 'multi-range-script-select.html#prev6S_' , 'multi-range-script-select-ref.html#prev6S_' ] ,
+ [ 'multi-range-script-select.html#prev7S_' , 'multi-range-script-select-ref.html#prev7S_' ] ,
+ // eDirPrevious, Shift+Accel+click
+ [ 'multi-range-script-select.html#prev1SA' , 'multi-range-script-select-ref.html#prev1SA' ] ,
+ [ 'multi-range-script-select.html#prev2SA' , 'multi-range-script-select-ref.html#prev2SA' ] ,
+ [ 'multi-range-script-select.html#prev3SA' , 'multi-range-script-select-ref.html#prev3SA' ] ,
+ [ 'multi-range-script-select.html#prev4SA' , 'multi-range-script-select-ref.html#prev4SA' ] ,
+ [ 'multi-range-script-select.html#prev5SA' , 'multi-range-script-select-ref.html#prev5SA' ] ,
+ [ 'multi-range-script-select.html#prev6SA' , 'multi-range-script-select-ref.html#prev6SA' ] ,
+ [ 'multi-range-script-select.html#prev7SA' , 'multi-range-script-select-ref.html#prev7SA' ] ,
+ // eDirPrevious, Accel+drag-select (adding an additional range)
+ [ 'multi-range-script-select.html#prev1AD' , 'multi-range-script-select-ref.html#prev1AD' ] ,
+ [ 'multi-range-script-select.html#prev7AD' , 'multi-range-script-select-ref.html#prev7AD' ] ,
+ // eDirPrevious, VK_RIGHT / LEFT
+ [ 'multi-range-script-select.html#prev1SR' , 'multi-range-script-select-ref.html#prev1SR' ] ,
+ [ 'multi-range-script-select.html#prev1SL' , 'multi-range-script-select-ref.html#prev1SL' ] ,
+ // eDirNext, Shift+click
+ [ 'multi-range-script-select.html#next1S_' , 'multi-range-script-select-ref.html#next1S_' ] ,
+ [ 'multi-range-script-select.html#next2S_' , 'multi-range-script-select-ref.html#next2S_' ] ,
+ [ 'multi-range-script-select.html#next3S_' , 'multi-range-script-select-ref.html#next3S_' ] ,
+ [ 'multi-range-script-select.html#next4S_' , 'multi-range-script-select-ref.html#next4S_' ] ,
+ [ 'multi-range-script-select.html#next5S_' , 'multi-range-script-select-ref.html#next5S_' ] ,
+ [ 'multi-range-script-select.html#next6S_' , 'multi-range-script-select-ref.html#next6S_' ] ,
+ [ 'multi-range-script-select.html#next7S_' , 'multi-range-script-select-ref.html#next7S_' ] ,
+ // eDirNext, Shift+Accel+click
+ [ 'multi-range-script-select.html#next1SA' , 'multi-range-script-select-ref.html#next1SA' ] ,
+ [ 'multi-range-script-select.html#next2SA' , 'multi-range-script-select-ref.html#next2SA' ] ,
+ [ 'multi-range-script-select.html#next3SA' , 'multi-range-script-select-ref.html#next3SA' ] ,
+ [ 'multi-range-script-select.html#next4SA' , 'multi-range-script-select-ref.html#next4SA' ] ,
+ [ 'multi-range-script-select.html#next5SA' , 'multi-range-script-select-ref.html#next5SA' ] ,
+ [ 'multi-range-script-select.html#next6SA' , 'multi-range-script-select-ref.html#next6SA' ] ,
+ [ 'multi-range-script-select.html#next7SA' , 'multi-range-script-select-ref.html#next7SA' ] ,
+ // eDirNext, Accel+drag-select (adding an additional range)
+ [ 'multi-range-script-select.html#next1AD' , 'multi-range-script-select-ref.html#next1AD' ] ,
+ [ 'multi-range-script-select.html#next7AD' , 'multi-range-script-select-ref.html#next7AD' ] ,
+ // eDirNext, VK_RIGHT / LEFT
+ [ 'multi-range-script-select.html#next1SR' , 'multi-range-script-select-ref.html#next1SR' ] ,
+ [ 'multi-range-script-select.html#next1SL' , 'multi-range-script-select-ref.html#next1SL' ] ,
+ ]);
+}
+
+var testIndex = 0;
+
+function nextTest() {
+ if (testIndex < tests.length) {
+ if (typeof(tests[testIndex]) == 'function') {
+ tests[testIndex]();
+ } else {
+ refTest(tests[testIndex][0],tests[testIndex][1]);
+ }
+ ++testIndex;
+ } else {
+ endTest();
+ }
+}
+function runTests() {
+ try {
+ if (window.parent) {
+ var parentDoc = window.parent.document;
+ extraCSSRule = parentDoc.styleSheets[parentDoc.styleSheets.length-1]
+ .insertRule("iframe#testframe{width:600px;height:400px}",0);
+ }
+ try {
+ caretBlinkTime = SpecialPowers.getIntPref("ui.caretBlinkTime");
+ } catch (e) {}
+ SpecialPowers.pushPrefEnv({'set': [['ui.caretBlinkTime', -1]]}, nextTest);
+ } catch(e) {
+ endTest();
+ }
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/base/tests/test_remote_frame.html b/layout/base/tests/test_remote_frame.html
new file mode 100644
index 000000000..765d526ae
--- /dev/null
+++ b/layout/base/tests/test_remote_frame.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ div, iframe {
+ position:absolute;
+ left:0; top:50px;
+ width:400px; height:400px;
+ transform: translateY(50px);
+ border:5px solid black;
+ }
+ </style>
+</head>
+ <body>
+ <div id="d" style="background:blue"></div>
+
+ <script type="application/javascript;version=1.7">
+ "use strict";
+
+ var referenceSnapshot;
+ var iterations = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ function pollForTestPass() {
+ var snapshot = snapshotWindow(window);
+ if (compareSnapshots(referenceSnapshot, snapshot, true)[0]) {
+ ok(true, "Test passed after " + iterations + " iterations");
+ SimpleTest.finish();
+ return;
+ }
+
+ ++iterations;
+ if (iterations == 20) {
+ todo(false, "We couldn't draw the frame, but at least we didn't crash");
+ SimpleTest.finish();
+ return;
+ }
+ setTimeout(pollForTestPass, 10);
+ }
+ function addRemoteFrame() {
+ let iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.src = "data:text/html,<html style='background:blue;'>";
+
+ document.body.appendChild(iframe);
+
+ pollForTestPass();
+ }
+ addEventListener("load", function() {
+ referenceSnapshot = snapshotWindow(window);
+ document.getElementById("d").style.display = 'none';
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.ipc.browser_frames.oop_by_default", true],
+ ["dom.mozBrowserFramesEnabled", true]
+ ]
+ }, addRemoteFrame);
+ });
+ </script>
+</body>
diff --git a/layout/base/tests/test_resize_flush.html b/layout/base/tests/test_resize_flush.html
new file mode 100644
index 000000000..471560747
--- /dev/null
+++ b/layout/base/tests/test_resize_flush.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1279202
+-->
+<head>
+ <title>Test for Bug 1279202</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1279202">Mozilla Bug 1279202</a>
+<iframe src="resize_flush_iframe.html" id="iframe" height="200" width="200" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1279202 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ var iframe = document.getElementById("iframe");
+ var doc = iframe.contentDocument.documentElement;
+ var win = iframe.contentWindow;
+ var body = iframe.contentDocument.body;
+
+ // Flush any pending layout changes before we start.
+ var width = doc.clientWidth;
+
+ // Resize the iframe
+ iframe.width = '300px';
+
+ // Flush pending style changes, but not layout ones. We do this twice because the first flush
+ // does a partial flush of the resize (setting the size on the pres context) which sets the
+ // need style flush flag again. The second call makes sure mNeedStyleFlush is false.
+ var color = win.getComputedStyle(body).getPropertyValue("background-color");
+ color = win.getComputedStyle(body).getPropertyValue("background-color");
+ is(color, "rgb(0, 128, 0)", "Style flush not completed when resizing an iframe!");
+
+ // Query the size of the inner document and make sure it has had a layout flush.
+ width = doc.clientWidth;
+
+ is(width, 300, "Layout flush not completed when resizing an iframe!");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_scroll_event_ordering.html b/layout/base/tests/test_scroll_event_ordering.html
new file mode 100644
index 000000000..be1764a7a
--- /dev/null
+++ b/layout/base/tests/test_scroll_event_ordering.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785588
+-->
+<head>
+ <title>Test for Bug 785588 --- ordering of scroll-related events</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785588">Mozilla Bug 785588</a>
+<div id="content">
+ <div id="d" style="border:2px solid black; width:100px; height:100px; overflow:auto">
+ <div id="inner" style="height:200px;">Hello</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var smoothScrollPref = "general.smoothScroll";
+
+var d = document.getElementById("d");
+d.scrollTop = 0;
+var inner = document.getElementById("inner");
+
+var state = "initial";
+
+function onFrame() {
+ is(state, "initial", "Must be in initial state");
+ ok(d.scrollTop > 0, "Must have scrolled by some amount (got " + d.scrollTop + ")");
+ state = "didOnFrame";
+}
+
+function onScroll() {
+ is(state, "didOnFrame", "Must have got requestAnimationFrame callback already");
+ ok(d.scrollTop > 0, "Must have scrolled by some amount (got " + d.scrollTop + ")");
+ SimpleTest.finish();
+}
+
+function doTest() {
+ window.getSelection().collapse(inner.firstChild, 0);
+ window.requestAnimationFrame(onFrame);
+ d.onscroll = onScroll;
+ sendKey("DOWN");
+}
+
+function prepareTest() {
+ // Start the test after we've gotten at least one rAF callback, to make sure
+ // that rAF is no longer throttled. (See bug 1145439.)
+ window.requestAnimationFrame(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, doTest);
+ });
+}
+
+SimpleTest.waitForFocus(prepareTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_scroll_selection_into_view.html b/layout/base/tests/test_scroll_selection_into_view.html
new file mode 100644
index 000000000..aea94b899
--- /dev/null
+++ b/layout/base/tests/test_scroll_selection_into_view.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scrolling selection into view</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var ANCHOR = 0;
+var FOCUS = 1;
+var win;
+
+function testCollapsed(id, vPercent, startAt, expected) {
+ var selection = SpecialPowers.wrap(win.getSelection())
+ .QueryInterface(SpecialPowers.Ci.nsISelectionPrivate);
+
+ var c = win.document.getElementById("c" + id);
+ var target = win.document.getElementById("target" + id);
+ if (target.contentDocument) {
+ selection = SpecialPowers.wrap(target.contentWindow.getSelection())
+ .QueryInterface(SpecialPowers.Ci.nsISelectionPrivate);
+ target = target.contentDocument.getElementById("target" + id);
+ }
+ selection.collapse(target.parentNode, 0);
+ c.scrollTop = startAt;
+ selection.scrollIntoView(FOCUS, true, vPercent, 0);
+ is(c.scrollTop, expected, "Scrolling " + target.id +
+ " into view with vPercent " + vPercent + ", starting at " + startAt);
+}
+
+function doTest() {
+ // Test scrolling an element smaller than the scrollport
+ testCollapsed("1", 0, 0, 400);
+ testCollapsed("1", 100, 0, 220);
+ testCollapsed("1", -1, 0, 220);
+ testCollapsed("1", 0, 500, 400);
+ testCollapsed("1", 100, 500, 220);
+ testCollapsed("1", -1, 500, 400);
+
+ // overflow:hidden elements should not be scrolled by selection
+ // scrolling-into-view
+ testCollapsed("2", 0, 0, 0);
+ testCollapsed("2", 100, 0, 0);
+ testCollapsed("2", -1, 0, 0);
+ testCollapsed("2", 0, 500, 500);
+ testCollapsed("2", 100, 500, 500);
+ testCollapsed("2", -1, 500, 500);
+
+ // Test scrolling an element larger than the scrollport
+ testCollapsed("3", 0, 0, 400);
+ testCollapsed("3", 100, 0, 500);
+ testCollapsed("3", -1, 0, 400);
+ testCollapsed("3", 0, 1000, 400);
+ testCollapsed("3", 100, 1000, 500);
+ // If the element can't be completely visible, we make the top edge
+ // visible.
+ testCollapsed("3", -1, 1000, 400);
+
+ // Test scrolling an element larger than the scrollport
+ testCollapsed("4", 0, 0, 400);
+ testCollapsed("4", 100, 0, 500);
+ testCollapsed("4", -1, 0, 400);
+ testCollapsed("4", 0, 1000, 400);
+ testCollapsed("4", 100, 1000, 500);
+ // If the element can't be completely visible, we make the top edge
+ // visible.
+ testCollapsed("4", -1, 1000, 400);
+
+ // Test that scrolling a translated element into view takes
+ // account of the transform.
+ testCollapsed("5", 0, 0, 400);
+
+ // Test that scrolling a scaled element into view takes
+ // account of the transform.
+ testCollapsed("6", 0, 0, 150);
+
+ // Test that scrolling an element with a translated, scrolling container
+ // into view takes account of the transform.
+ testCollapsed("7", 0, 0, 400);
+
+ win.close();
+ SimpleTest.finish();
+}
+
+function openWindow() {
+ win = open("scroll_selection_into_view_window.html", "_blank", "width=500,height=350");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(openWindow);
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/layout/base/tests/test_scroll_snapping.html b/layout/base/tests/test_scroll_snapping.html
new file mode 100644
index 000000000..1ba862ee1
--- /dev/null
+++ b/layout/base/tests/test_scroll_snapping.html
@@ -0,0 +1,806 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scroll snapping</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<p id="display"></p>
+<div id="sc" style="margin: 0px; padding: 0px; overflow: scroll; width: 500px; height: 250px;">
+ <div id="sd" style="margin: 0px; padding: 0px; width: 1500px; height: 1250px;"></div>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var testCases = [
+ {
+ "description" : "Proximity + Within proximity => Snaps to point (Right)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "525px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_RIGHT",
+ "expectedX" : 525,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Proximity + Within proximity => Snaps to point (Left)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "475px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_LEFT",
+ "expectedX" : 475,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Proximity + Within proximity => Snaps to point (Up)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 475px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_UP",
+ "expectedX" : 500,
+ "expectedY" : 475,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Proximity + Within proximity => Snaps to point (Down)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 525px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 525,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Proximity + Beyond proximity => Does not snap to point (Right)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "700px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_RIGHT",
+ "expectedX" : 700,
+ "expectedY" : 500,
+ "notExpected" : true
+ },
+
+ {
+ "description" : "Proximity + Beyond proximity => Does not snap to point (Left)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "300px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_LEFT",
+ "expectedX" : 300,
+ "expectedY" : 500,
+ "notExpected" : true
+ },
+
+ {
+ "description" : "Proximity + Beyond proximity => Does not snap to point (Up)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 300px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_UP",
+ "expectedX" : 500,
+ "expectedY" : 300,
+ "notExpected" : true
+ },
+
+ {
+ "description" : "Proximity + Beyond proximity => Does not snap to point (Down)",
+ "scrollSnapType" : "proximity",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 700px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 700,
+ "notExpected" : true
+ },
+
+ {
+ "description" : "Mandatory + Beyond proximity => Snaps to point (Right)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "700px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_RIGHT",
+ "expectedX" : 700,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + Beyond proximity => Snaps to point (Left)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "300px 500px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_LEFT",
+ "expectedX" : 300,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + Beyond proximity => Snaps to point (Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 300px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_UP",
+ "expectedX" : 500,
+ "expectedY" : 300,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + Beyond proximity => Snaps to point (Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 700px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 700,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + No snap points => Does not snap or scroll (Left)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_LEFT",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + No snap points => Does not snap or scroll (Right)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_RIGHT",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + No snap points => Does not snap or scroll (Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_UP",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + No snap points => Does not snap or scroll (Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + scroll-snap-destination + Start of page + No snap point at start of page => Does not snap to top of page (Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 200,
+ "key" : "VK_UP",
+ "expectedX" : 0,
+ "expectedY" : 200,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + scroll-snap-destination + Start of page + No snap point at start of page => Does not snap to top of page (Page Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 200,
+ "key" : "VK_PAGE_UP",
+ "expectedX" : 0,
+ "expectedY" : 200,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + scroll-snap-destination + Start of page + No snap point at start of page => Snaps to highest snap point (Home)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 200,
+ "key" : "VK_HOME",
+ "expectedX" : 0,
+ "expectedY" : 475,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-x + scroll-snap-destination + Start of page + No snap point at start of page => Does not snap to left of page (Left)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(500px)",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 200,
+ "initialY" : 0,
+ "key" : "VK_LEFT",
+ "expectedX" : 200,
+ "expectedY" : 0,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + negative scroll-snap-destination + End of page + No snap point at end of page => Does not snap to end of page (Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "-25px -25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 800,
+ "key" : "VK_DOWN",
+ "expectedX" : 0,
+ "expectedY" : 800,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + negative scroll-snap-destination + End of page + No snap point at end of page => Does not snap to end of page (Page Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "-25px -25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 800,
+ "key" : "VK_PAGE_DOWN",
+ "expectedX" : 0,
+ "expectedY" : 800,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y + negative scroll-snap-destination + End of page + No snap point at end of page => Snaps to lowest snap point (End)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(500px)",
+ "scrollSnapDestination" : "-25px -25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 0,
+ "initialY" : 800,
+ "key" : "VK_END",
+ "expectedX" : 0,
+ "expectedY" : 525,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-coordinate + No snap point at start of page => Does not snap to top of page (Home)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 100px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_HOME",
+ "expectedX" : 500,
+ "expectedY" : 100,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-coordinate + No snap point at end of page => Does not snap to end of page (End)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "500px 900px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_END",
+ "expectedX" : 500,
+ "expectedY" : 900,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y greater than scrolling rect + positive non-zero scroll-snap-destination => No snap points, does not scroll or snap (Left)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(1500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_LEFT",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y greater than scrolling rect + positive non-zero scroll-snap-destination => No snap points, does not scroll or snap (Right)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(1500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_RIGHT",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y greater than scrolling rect + positive non-zero scroll-snap-destination => No snap points, does not scroll or snap (Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(1500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_UP",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-y greater than scrolling rect + positive non-zero scroll-snap-destination => No snap points, does not scroll or snap (Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "repeat(1500px)",
+ "scrollSnapDestination" : "25px 25px",
+ "scrollSnapCoordinate" : "none",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 500,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Right #1)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 0,
+ "initialY" : 0,
+ "key" : "VK_RIGHT",
+ "expectedX" : 120,
+ "expectedY" : 0,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Right #2)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 120,
+ "initialY" : 0,
+ "key" : "VK_RIGHT",
+ "expectedX" : 200,
+ "expectedY" : 0,
+ "notExpected" : false
+ },
+
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Right #3)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 200,
+ "initialY" : 0,
+ "key" : "VK_RIGHT",
+ "expectedX" : 220,
+ "expectedY" : 0,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Up #1)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 0,
+ "initialY" : 300,
+ "key" : "VK_UP",
+ "expectedX" : 0,
+ "expectedY" : 220,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Up #2)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 0,
+ "initialY" : 220,
+ "key" : "VK_UP",
+ "expectedX" : 0,
+ "expectedY" : 200,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Mandatory + scroll-snap-points-{x|y} + scroll-snap-coordinate with two points => Interval and element snapping points are combined. Test 1 snap point generated by scroll-snap-points-{x|y} and two generated by scroll-snap-coordinate, ensure that they are evaluated as snap points. (Up #3)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "repeat(200px)",
+ "scrollSnapPointsY" : "repeat(200px)",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "120px 120px, 220px 220px",
+ "initialX" : 0,
+ "initialY" : 200,
+ "key" : "VK_UP",
+ "expectedX" : 0,
+ "expectedY" : 120,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the next snap point in the direction of the scroll is selected rather than a closer point in the opposite direction (page Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "600px 600px, 475px 475px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_PAGE_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 600,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the next snap point in the direction of the scroll is selected rather than a closer point in the opposite direction (Page Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "400px 400px, 525px 525px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_PAGE_UP",
+ "expectedX" : 500,
+ "expectedY" : 400,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination. Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 500px. (Page Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "525px 525px, 550px 550px, 800px 800px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_PAGE_DOWN",
+ "expectedX" : 500,
+ "expectedY" : 550,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination. Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 500px. (Page Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "475px 475px, 450px 450px, 200px 200px",
+ "initialX" : 500,
+ "initialY" : 500,
+ "key" : "VK_PAGE_UP",
+ "expectedX" : 500,
+ "expectedY" : 450,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination. Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected. Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 500px. (Page Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "50px 50px, 800px 800px, 900px 900px",
+ "initialX" : 100,
+ "initialY" : 100,
+ "key" : "VK_PAGE_DOWN",
+ "expectedX" : 100,
+ "expectedY" : 800,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination. Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected. Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 500px. (Page Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "950px 950px, 200px 200px, 100px 100px",
+ "initialX" : 900,
+ "initialY" : 900,
+ "key" : "VK_PAGE_UP",
+ "expectedX" : 900,
+ "expectedY" : 200,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when scrolling by lines up,down,left,or right, that the closest snap point to the destination in the direction of travel is selected. Setup - Two snap points in the direction of travel and one in the opposite direction. Snap point in opposite direction is closest to the destination but must not be selected. (Down)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "50px 50px, 800px 800px, 900px 900px",
+ "initialX" : 100,
+ "initialY" : 100,
+ "key" : "VK_PAGE_DOWN",
+ "expectedX" : 100,
+ "expectedY" : 800,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when scrolling by lines up,down,left,or right, that the closest snap point to the destination in the direction of travel is selected. Setup - Two snap points in the direction of travel and one in the opposite direction. Snap point in opposite direction is closest to the destination but must not be selected. (Up)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "950px 950px, 200px 200px, 100px 100px",
+ "initialX" : 900,
+ "initialY" : 900,
+ "key" : "VK_PAGE_UP",
+ "expectedX" : 900,
+ "expectedY" : 200,
+ "notExpected" : false
+ },
+
+ {
+ "description" : "Ensure that when scrolling by lines up,down,left,or right, that the closest snap point to the destination in the direction of travel is selected. Setup - Two snap points in the direction of travel and one in the opposite direction. Snap point in opposite direction is closest to the destination but must not be selected. (Left)",
+ "scrollSnapType" : "mandatory",
+ "scrollSnapPointsX" : "none",
+ "scrollSnapPointsY" : "none",
+ "scrollSnapDestination" : "0px 0px",
+ "scrollSnapCoordinate" : "950px 950px, 200px 200px, 100px 100px",
+ "initialX" : 900,
+ "initialY" : 900,
+ "key" : "VK_LEFT",
+ "expectedX" : 200,
+ "expectedY" : 900,
+ "notExpected" : false
+ }
+];
+
+var step = 0;
+var sc; // Scroll Container
+var sd; // Scrolled Div
+
+var lastScrollTop;
+var lastScrollLeft;
+var stopFrameCount;
+
+// The tests should work the same way when all the values for the scroll
+// container are provided in percentages. To assert that, we just duplicate all
+// the test cases and replace the pixel values related to the scroll container
+// with percentage values, based on its clientWidth/Height sespectively.
+function addPercentageTests() {
+ var width = sc.clientWidth;
+ var height = sc.clientHeight;
+ var pxRegexp = /(\d+)px/;
+ var rewriteW = (_, w) => (parseInt(w, 10) / width * 100) + "%";
+ var rewriteH = (_, h) => (parseInt(h, 10) / height * 100) + "%";
+ testCases = testCases.concat(testCases.map(testCase => Object.assign({}, testCase, {
+ description: "With Percentages: " + testCase.description,
+ scrollSnapPointsX: testCase.scrollSnapPointsX.replace(pxRegexp, rewriteW),
+ scrollSnapPointsY: testCase.scrollSnapPointsY.replace(pxRegexp, rewriteH),
+ scrollSnapDestination: testCase.scrollSnapDestination
+ .replace(pxRegexp, rewriteW).replace(pxRegexp, rewriteH),
+ })));
+}
+
+function initTest() {
+ var testCase = testCases[step];
+ sc.style.scrollSnapType = testCase.scrollSnapType;
+ sc.style.scrollSnapPointsX = testCase.scrollSnapPointsX;
+ sc.style.scrollSnapPointsY = testCase.scrollSnapPointsY;
+ sc.style.scrollSnapDestination = testCase.scrollSnapDestination;
+ sd.style.scrollSnapCoordinate = testCase.scrollSnapCoordinate;
+ sc.scrollTo(testCase.initialX, testCase.initialY);
+ sc.focus();
+ synthesizeKey(testCase.key, {});
+
+ stopFrameCount = 0;
+ lastScrollTop = sc.scrollTop;
+ lastScrollLeft = sc.scrollLeft;
+
+ window.requestAnimationFrame(waitForScrollStop);
+}
+
+function waitForScrollStop() {
+ if (stopFrameCount > 30) {
+ // We have the same position for 30 consecutive frames -- we are stopped
+ testScrolling();
+ } else {
+ // Still moving
+ if (lastScrollTop == sc.scrollTop && lastScrollLeft == sc.scrollLeft) {
+ stopFrameCount++;
+ } else {
+ stopFrameCount = 0;
+ lastScrollTop = sc.scrollTop;
+ lastScrollLeft = sc.scrollLeft;
+ }
+ window.requestAnimationFrame(waitForScrollStop);
+ }
+}
+
+function testScrollSnapping() {
+ sc = document.getElementById("sc");
+ sd = document.getElementById("sd");
+
+ addPercentageTests();
+
+ initTest();
+}
+
+function testScrolling() {
+ var testCase = testCases[step];
+ if (document.getElementById('display').style.direction == "rtl") {
+ if (testCase.notExpected) {
+ isnot("(" + -sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + -testCase.expectedX + "," + testCase.expectedY + ")",
+ "rtl: " + testCase.description);
+ } else {
+ is("(" + -sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + -testCase.expectedX + "," + testCase.expectedY + ")",
+ "rtl: " + testCase.description);
+ }
+ } else {
+ // ltr direction
+ if (testCase.notExpected) {
+ isnot("(" + sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + testCase.expectedX + "," + testCase.expectedY + ")",
+ "ltr: " + testCase.description);
+ } else {
+ is("(" + sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + testCase.expectedX + "," + testCase.expectedY + ")",
+ "ltr: " + testCase.description);
+ }
+ }
+
+ step++;
+ if (step < Object.keys(testCases).length) {
+ initTest();
+ } else {
+ if (document.getElementById('display').style.direction == "rtl") {
+ SimpleTest.finish();
+ } else {
+ // Do everything again, but with rtl
+ document.getElementById('display').style.direction = "rtl";
+ step = 0;
+ initTest();
+ }
+ }
+}
+
+function doTest() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["layout.css.scroll-snap.enabled", true],
+ ["layout.css.scroll-snap.proximity-threshold", 100]]},
+ testScrollSnapping);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(doTest);
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/layout/base/tests/test_scroll_snapping_scrollbars.html b/layout/base/tests/test_scroll_snapping_scrollbars.html
new file mode 100644
index 000000000..7caf62a59
--- /dev/null
+++ b/layout/base/tests/test_scroll_snapping_scrollbars.html
@@ -0,0 +1,349 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scroll snapping</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<p id="display"></p>
+<div id="sc" style="margin: 0px; padding: 0px; overflow: scroll; width:250px; height: 250px;">
+ <div id="sd" style="margin: 0px; padding: 0px; width: 1250px; height: 1250px;"></div>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var runtime = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"]
+ .getService(SpecialPowers.Ci.nsIXULRuntime);
+var isMac = navigator.platform.indexOf("Mac") != -1;
+var isGtk = runtime.widgetToolkit.indexOf("gtk") != -1;
+var isWin = navigator.platform.indexOf("Win") != -1;
+var isSN = /mac os x 10\.6/.test(navigator.userAgent.toLowerCase());
+
+// Half of the scrollbar control width, in CSS pixels
+var scrollbarOffset = isWin ? 8 : 5;
+
+// OSX 10.6 scroll bar thumbs are off-center due to the bundling of buttons on one end
+// of the scroll bar frame.
+var scrollbarCenter = isSN ? 100 : 125;
+
+var testCases = [
+ {
+ "description" : "Drag scrollbar left, expect scroll snapping.",
+ "snapCoord" : "500px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : scrollbarCenter, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : -10, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Drag scrollbar right, expect scroll snapping.",
+ "snapCoord" : "500px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : scrollbarCenter, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 10, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Drag scrollbar up, expect scroll snapping.",
+ "snapCoord" : "500px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : scrollbarCenter },
+ "mouseOffset" : { "x" : 0, "y" : -10 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Drag scrollbar down, expect scroll snapping.",
+ "snapCoord" : "500px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : scrollbarCenter },
+ "mouseOffset" : { "x" : 0, "y" : 10 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Page scrollbar left, expect scroll snapping.",
+ "snapCoord" : "500px 500px, 1000px 500px",
+ "startScroll" : { "x" : 1000, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 50, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Page scrollbar right, expect scroll snapping.",
+ "snapCoord" : "500px 500px, 0px 500px",
+ "startScroll" : { "x" : 0, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 200, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Page scrollbar up, expect scroll snapping.",
+ "snapCoord" : "500px 500px, 500px 1000px",
+ "startScroll" : { "x" : 500, "y" : 1000 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : 50 },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Page scrollbar down, expect scroll snapping.",
+ "snapCoord" : "500px 500px, 500px 0px",
+ "startScroll" : { "x" : 500, "y" : 0 },
+ "endScroll" : { "x" : 500, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : 200 },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : true,
+ "runGtk" : true,
+ "runWin" : true
+ },
+ {
+ "description" : "Click scrollbar left button, expect scroll snapping.",
+ "snapCoord" : "50px 500px, 250px 500px, 500px 500px, 750px 500px, 950px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 250, "y" : 500 },
+ "mousePosition" : { "x" : scrollbarOffset, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Hold scrollbar left button until repeating, expect scroll snapping.",
+ "snapCoord" : "50px 500px, 500px 500px, 950px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 50, "y" : 500 },
+ "mousePosition" : { "x" : scrollbarOffset, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "500",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Click scrollbar right button, expect scroll snapping.",
+ "snapCoord" : "50px 500px, 250px 500px, 500px 500px, 750px 500px, 950px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 750, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset * 3, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Hold scrollbar right button until repeating, expect scroll snapping.",
+ "snapCoord" : "50px 500px, 500px 500px, 950px 500px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 950, "y" : 500 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset * 3, "y" : 250 - scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "500",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Click scrollbar up button, expect scroll snapping.",
+ "snapCoord" : "500px 50px, 500px 250px, 500px 500px, 500px 750px, 500px 950px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 250 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Hold scrollbar up button until repeating, expect scroll snapping.",
+ "snapCoord" : "500px 50px, 500px 500px, 500px 950px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 50 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : scrollbarOffset },
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "500",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Click scrollbar down button, expect scroll snapping.",
+ "snapCoord" : "500px 50px, 500px 250px, 500px 500px, 500px 750px, 500px 950px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 750 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : 250 - scrollbarOffset * 3},
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "0",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+ {
+ "description" : "Hold scrollbar down button until repeating, expect scroll snapping.",
+ "snapCoord" : "500px 50px, 500px 500px, 500px 950px",
+ "startScroll" : { "x" : 500, "y" : 500 },
+ "endScroll" : { "x" : 500, "y" : 950 },
+ "mousePosition" : { "x" : 250 - scrollbarOffset, "y" : 250 - scrollbarOffset * 3},
+ "mouseOffset" : { "x" : 0, "y" : 0 },
+ "duration" : "500",
+ "runMac" : false, // OSX does not have have line-scroll buttons
+ "runGtk" : false, // Some GTK themes may not have scroll buttons
+ "runWin" : true
+ },
+
+];
+
+var step = 0;
+var sc; // Scroll Container
+var sd; // Scrolled Div
+
+var lastScrollTop;
+var lastScrollLeft;
+var stopFrameCount;
+var winUtils = SpecialPowers.DOMWindowUtils;
+
+function doTest() {
+ var testCase = testCases[step];
+
+ stopFrameCount = 0;
+ lastScrollTop = sc.scrollTop;
+ lastScrollLeft = sc.scrollLeft;
+
+ sc.scrollTo(testCase.startScroll.x, testCase.startScroll.y);
+ sc.style.scrollSnapType = "mandatory";
+ sd.style.scrollSnapCoordinate = testCase.snapCoord;
+
+ synthesizeMouse(sc,
+ testCase.mousePosition.x,
+ testCase.mousePosition.y,
+ { type: "mousedown" });
+
+ synthesizeMouse(sc,
+ testCase.mousePosition.x + testCase.mouseOffset.x,
+ testCase.mousePosition.y + testCase.mouseOffset.y,
+ { type: "mousemove" });
+
+ stopFrameCount = 0;
+ waitForScrollStart();
+}
+
+function waitForScrollStart() {
+ // Wait for up to 30 frames for scrolling to start
+ var testCase = testCases[step];
+ if (testCase.startScroll.y != sc.scrollTop
+ || testCase.startScroll.x != sc.scrollLeft
+ || ++stopFrameCount < 30) {
+ window.requestAnimationFrame(doMouseUp);
+ } else {
+ window.requestAnimationFrame(waitForScrollStart);
+ }
+}
+
+function doMouseUp() {
+ var testCase = testCases[step];
+ isnot("(" + sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + testCase.startScroll.x +"," + testCase.startScroll.y + ")",
+ "Step " + step + ": Synthesized mouse events move scroll position. ("
+ + testCase.description + ")");
+
+ window.setTimeout(function() {
+ synthesizeMouse(sc,
+ testCase.mousePosition.x + testCase.mouseOffset.x,
+ testCase.mousePosition.y + testCase.mouseOffset.y,
+ { type: "mouseup" });
+
+ stopFrameCount = 0;
+ window.requestAnimationFrame(waitForScrollStop);
+ }, testCase.duration);
+}
+
+function waitForScrollStop() {
+ if (stopFrameCount > 30) {
+ // We have the same position for 30 consecutive frames -- we are stopped
+ verifyTest();
+ } else {
+ // Still moving
+ if (lastScrollTop == sc.scrollTop && lastScrollLeft == sc.scrollLeft) {
+ stopFrameCount++;
+ } else {
+ stopFrameCount = 0;
+ lastScrollTop = sc.scrollTop;
+ lastScrollLeft = sc.scrollLeft;
+ }
+ window.requestAnimationFrame(waitForScrollStop);
+ }
+}
+
+function verifyTest() {
+ // Test ended, check if scroll position matches expected position
+ var testCase = testCases[step];
+ is("(" + sc.scrollLeft + "," + sc.scrollTop + ")",
+ "(" + testCase.endScroll.x +"," + testCase.endScroll.y + ")",
+ "Step " + step + ": " + testCase.description);
+
+ // Find next test to run
+ while (true) {
+ if (++step == testCases.length) {
+ SimpleTest.finish();
+ break;
+ } else {
+ testCase = testCases[step];
+ if ((testCase.runGtk && isGtk)
+ || (testCase.runMac && isMac)
+ || (testCase.runWin && isWin)) {
+ doTest();
+ break;
+ }
+ }
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Delays added to allow synthesized mouse " +
+ "events to trigger scrollbar repeating scrolls.");
+addLoadEvent(function() {
+ sc = document.getElementById("sc");
+ sd = document.getElementById("sd");
+ SpecialPowers.pushPrefEnv({
+ "set": [["layout.css.scroll-snap.enabled", true],
+ ["layout.css.scroll-snap.proximity-threshold", 100]]},
+ doTest);
+});
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints.html b/layout/base/tests/test_transformed_scrolling_repaints.html
new file mode 100644
index 000000000..00e6cbfd5
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="setPrefAndStartTest()">
+<div id="t" style="-moz-transform: scale(1.2, 1.2); -moz-transform-origin:top left; width:200px; height:500px; background:yellow; overflow:auto">
+ <div style="height:40px;">Hello</div>
+ <div id="e" style="height:30px; background:lime">Kitty</div>
+ <div style="height:800px; background:yellow">Kitty</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function startTest() {
+ // Do a couple of scrolls to ensure we've triggered activity heuristics.
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 10;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ t.scrollTop = 15;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+function setPrefAndStartTest() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ // Need a timeout here to allow paint unsuppression before we start the test
+ function() {
+ setTimeout(startTest, 0);
+ }
+ );
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints_2.html b/layout/base/tests/test_transformed_scrolling_repaints_2.html
new file mode 100644
index 000000000..84de2dd4d
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints_2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them (1.1 scale)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="setPrefAndStartTest()">
+<div id="t" style="-moz-transform: scale(1.1, 1.1); -moz-transform-origin:top left; width:200px; height:100px; background:yellow; overflow:hidden">
+ <div style="height:40px;"></div>
+ <div id="e" style="height:30px; background:lime"></div>
+ <div style="height:300px; background:yellow"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function startTest() {
+ // Do a couple of scrolls to ensure we've triggered activity heuristics
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 10;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ t.scrollTop = 20;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+function setPrefAndStartTest() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ // Need a timeout here to allow paint unsuppression before we start the test
+ function() {
+ setTimeout(startTest, 0);
+ }
+ );
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints_3.html b/layout/base/tests/test_transformed_scrolling_repaints_3.html
new file mode 100644
index 000000000..d8cbd0891
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints_3.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ function() {
+ window.open("transformed_scrolling_repaints_3_window.html", "transformed_scrolling_repaints_3",
+ "chrome,width=300,height=400,scrollbars=no");
+ }
+);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/textarea-invalid-ref.html b/layout/base/tests/textarea-invalid-ref.html
new file mode 100644
index 000000000..c5607603d
--- /dev/null
+++ b/layout/base/tests/textarea-invalid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea style="background-color:red">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-maxlength-invalid-change.html b/layout/base/tests/textarea-maxlength-invalid-change.html
new file mode 100644
index 000000000..8c1691b91
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="2">fooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-ui-invalid-change.html b/layout/base/tests/textarea-maxlength-ui-invalid-change.html
new file mode 100644
index 000000000..eed6de90f
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is -moz-ui-invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="2">fooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-ui-valid-change.html b/layout/base/tests/textarea-maxlength-ui-valid-change.html
new file mode 100644
index 000000000..b7385ee6d
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is -moz-ui-valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {}); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="3">foooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-valid-before-change.html b/layout/base/tests/textarea-maxlength-valid-before-change.html
new file mode 100644
index 000000000..3466d310a
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is valid until the user edits it, even if it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <textarea id="textarea" maxlength="2">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-maxlength-valid-change.html b/layout/base/tests/textarea-maxlength-valid-change.html
new file mode 100644
index 000000000..c845111ac
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {}); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey('VK_BACK_SPACE', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="3">foooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-invalid-change.html b/layout/base/tests/textarea-minlength-invalid-change.html
new file mode 100644
index 000000000..3e26feada
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('o', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="4">fo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-ui-invalid-change.html b/layout/base/tests/textarea-minlength-ui-invalid-change.html
new file mode 100644
index 000000000..99ce7721e
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is -moz-ui-invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('o', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="4">fo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-ui-valid-change.html b/layout/base/tests/textarea-minlength-ui-valid-change.html
new file mode 100644
index 000000000..1449b5fa2
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is -moz-ui-valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('o', {}); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey('o', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="3">f</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-valid-before-change.html b/layout/base/tests/textarea-minlength-valid-before-change.html
new file mode 100644
index 000000000..6fd7ad979
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is valid until the user edits it, even if it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <textarea id="textarea" minlength="5">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-minlength-valid-change.html b/layout/base/tests/textarea-minlength-valid-change.html
new file mode 100644
index 000000000..081d5d21a
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey('o', {}); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey('o', {});
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="3">f</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-valid-ref.html b/layout/base/tests/textarea-valid-ref.html
new file mode 100644
index 000000000..547b4fb7c
--- /dev/null
+++ b/layout/base/tests/textarea-valid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea style="background-color:green">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/transformed_scrolling_repaints_3_window.html b/layout/base/tests/transformed_scrolling_repaints_3_window.html
new file mode 100644
index 000000000..26ea43eec
--- /dev/null
+++ b/layout/base/tests/transformed_scrolling_repaints_3_window.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</title>
+ <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)" style="background:white;">
+<iframe id="t" style="-moz-transform: scale(0.48979); -moz-transform-origin:top left; width:500px; height:600px;"
+ src="data:text/html,
+<body style='background:yellow;'>
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p id='e'>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+</body>"></iframe>
+<pre id="test">
+<script type="application/javascript">
+var SimpleTest = window.opener.SimpleTest;
+var SpecialPowers = window.opener.SpecialPowers;
+var is = window.opener.is;
+var t, e, utils, iterations;
+var smoothScrollPref = "general.smoothScroll";
+
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTest);
+}
+function runTest() {
+ t = document.getElementById("t");
+ e = t.contentDocument.getElementById("e");
+ t.contentWindow.scrollTo(0,0);
+ utils = SpecialPowers.getDOMWindowUtils(window);
+ iterations = 0;
+
+ // Do a couple of scrolls to ensure we've triggered activity heuristics.
+ waitForAllPaintsFlushed(function () {
+ t.contentWindow.scrollByLines(1);
+ waitForAllPaintsFlushed(function () {
+ t.contentWindow.scrollByLines(1);
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ t.contentWindow.scrollByLines(1);
+ waitForAllPaintsFlushed(nextIteration);
+ });
+ });
+ });
+}
+function nextIteration() {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ if (++iterations == 10) {
+ SimpleTest.finish();
+ window.close();
+ } else {
+ t.contentWindow.scrollByLines(1);
+ waitForAllPaintsFlushed(nextIteration);
+ }
+}
+</script>
+</pre>
+</body>
+</html>